From 08b24fe753191482030db55690c0a33819977d1a Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 15:31:26 +0530 Subject: [PATCH 01/22] Adding model delete and MD delete/deactivate APIs --- ads/aqua/extension/deployment_handler.py | 8 ++++++++ ads/aqua/extension/model_handler.py | 2 ++ ads/aqua/model/model.py | 9 +++++++++ ads/aqua/modeldeployment/deployment.py | 9 +++++++++ 4 files changed, 28 insertions(+) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 3e3a6c277..c8ae63273 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -54,6 +54,14 @@ def get(self, id=""): else: raise HTTPError(400, f"The request {self.request.path} is invalid.") + @handle_exceptions + def delete(self,model_deployment_id): + return self.finish(AquaDeploymentApp().delete(model_deployment_id)) + + @handle_exceptions + def put(self,model_deployment_id): + return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + @handle_exceptions def post(self, *args, **kwargs): """ diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 5fa25992f..f6c1d6c83 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -73,6 +73,8 @@ def delete(self, id=""): paths = url_parse.path.strip("/") if paths.startswith("aqua/model/cache"): return self.finish(AquaModelApp().clear_model_list_cache()) + elif id: + return self.finish(AquaModelApp().delete_registered_model(id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index b27d28c49..6920e653a 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -323,6 +323,15 @@ def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaMod return model_details + @telemetry(entry_point="plugin=model&action=delete", name="aqua") + def delete_registered_model(self,model_id): + ds_model=DataScienceModel.from_id(model_id) + is_registered_model=ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None) + if is_registered_model: + return ds_model.delete() + else: + raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + def _fetch_metric_from_metadata( self, custom_metadata_list: ModelCustomMetadata, diff --git a/ads/aqua/modeldeployment/deployment.py b/ads/aqua/modeldeployment/deployment.py index 654e00dc8..62de4d0fb 100644 --- a/ads/aqua/modeldeployment/deployment.py +++ b/ads/aqua/modeldeployment/deployment.py @@ -485,6 +485,15 @@ def list(self, **kwargs) -> List["AquaDeployment"]: return results + + @telemetry(entry_point="plugin=deployment&action=delete", name="aqua") + def delete(self,model_deployment_id:str): + return self.ds_client.delete_model_deployment(model_deployment_id=model_deployment_id).data + + @telemetry(entry_point="plugin=deployment&action=deactivate",name="aqua") + def deactivate(self,model_deployment_id:str): + return self.ds_client.deactivate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=get", name="aqua") def get(self, model_deployment_id: str, **kwargs) -> "AquaDeploymentDetail": """Gets the information of Aqua model deployment. From 75a5f5cecf82e674a3d3136a4a5142a95bd6b8cf Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 16:02:44 +0530 Subject: [PATCH 02/22] Adding MD activate API --- ads/aqua/extension/deployment_handler.py | 24 ++++++++++++++++++++++-- ads/aqua/modeldeployment/deployment.py | 5 ++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index c8ae63273..508a2b0a5 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -59,8 +59,28 @@ def delete(self,model_deployment_id): return self.finish(AquaDeploymentApp().delete(model_deployment_id)) @handle_exceptions - def put(self,model_deployment_id): - return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + def put(self,*args,**kwargs): + try: + input_data = self.get_json_body() + except Exception as ex: + raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex + if not input_data: + raise HTTPError(400, Errors.NO_INPUT_DATA) + + # required input parameters + model_deployment_id = input_data.get("model_deployment_id") + if not model_deployment_id: + raise HTTPError( + 400, Errors.MISSING_REQUIRED_PARAMETER.format("model_deployment_id") + ) + url_parse = urlparse(self.request.path) + paths = url_parse.path.strip("/") + if paths.startswith("aqua/deployments/activate"): + return self.finish(AquaDeploymentApp().activate(model_deployment_id)) + elif paths.startswith("aqua/deployments/deactivate"): + return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + else: + raise HTTPError(400, f"The request {self.request.path} is invalid.") @handle_exceptions def post(self, *args, **kwargs): diff --git a/ads/aqua/modeldeployment/deployment.py b/ads/aqua/modeldeployment/deployment.py index 62de4d0fb..3fdceac04 100644 --- a/ads/aqua/modeldeployment/deployment.py +++ b/ads/aqua/modeldeployment/deployment.py @@ -485,7 +485,6 @@ def list(self, **kwargs) -> List["AquaDeployment"]: return results - @telemetry(entry_point="plugin=deployment&action=delete", name="aqua") def delete(self,model_deployment_id:str): return self.ds_client.delete_model_deployment(model_deployment_id=model_deployment_id).data @@ -494,6 +493,10 @@ def delete(self,model_deployment_id:str): def deactivate(self,model_deployment_id:str): return self.ds_client.deactivate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=activate",name="aqua") + def activate(self,model_deployment_id:str): + return self.ds_client.activate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=get", name="aqua") def get(self, model_deployment_id: str, **kwargs) -> "AquaDeploymentDetail": """Gets the information of Aqua model deployment. From c7ff5847b389357cd7713b769d6da4a056d2eb52 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 22:12:22 +0530 Subject: [PATCH 03/22] Adding edit registered model api --- ads/aqua/extension/errors.py | 1 + ads/aqua/extension/model_handler.py | 23 +++++++++ ads/aqua/model/model.py | 72 +++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/ads/aqua/extension/errors.py b/ads/aqua/extension/errors.py index d5e44944c..9829ff9e4 100644 --- a/ads/aqua/extension/errors.py +++ b/ads/aqua/extension/errors.py @@ -8,3 +8,4 @@ class Errors(str): NO_INPUT_DATA = "No input data provided." MISSING_REQUIRED_PARAMETER = "Missing required parameter: '{}'" MISSING_ONEOF_REQUIRED_PARAMETER = "Either '{}' or '{}' is required." + INVALID_VALUE_OF_PARAMETER = "Invalid value of parameter: '{}'" diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index f6c1d6c83..49f30457a 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -8,6 +8,7 @@ from tornado.web import HTTPError from ads.aqua.common.decorator import handle_exceptions +from ads.aqua.common.enums import InferenceContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import get_hf_model_info, list_hf_models from ads.aqua.extension.base_handler import AquaAPIhandler @@ -139,6 +140,28 @@ def post(self, *args, **kwargs): ) ) + @handle_exceptions + def put(self,id): + try: + input_data = self.get_json_body() + except Exception as ex: + raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex + + if not input_data: + raise HTTPError(400, Errors.NO_INPUT_DATA) + + inference_container=input_data.get('inference_container') + if inference_container is not None and inference_container not in [ + InferenceContainerTypeFamily.AQUA_TGI_CONTAINER_FAMILY, + InferenceContainerTypeFamily.AQUA_VLLM_CONTAINER_FAMILY, + InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY + ]: + raise HTTPError(400,Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container")) + enable_finetuning=input_data.get('enable_finetuning') + task=input_data.get('task') + return self.finish(AquaModelApp().edit_registered_model(id,inference_container,enable_finetuning,task)) + + class AquaModelLicenseHandler(AquaAPIhandler): """Handler for Aqua Model license REST APIs.""" diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 6920e653a..27883e043 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -10,11 +10,11 @@ import oci from cachetools import TTLCache from huggingface_hub import snapshot_download -from oci.data_science.models import JobRun, Model +from oci.data_science.models import JobRun, Model, UpdateModelDetails, Metadata from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger from ads.aqua.app import AquaApp -from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags +from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags, FineTuningContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( LifecycleStatus, @@ -75,7 +75,7 @@ TENANCY_OCID, ) from ads.model import DataScienceModel -from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem +from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem, MetadataCustomCategory from ads.telemetry import telemetry @@ -332,6 +332,72 @@ def delete_registered_model(self,model_id): else: raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + @telemetry(entry_point="plugin=model&action=delete", name="aqua") + def edit_registered_model(self,id,inference_container,enable_finetuning,task): + """Edits the default config of unverified registered model. + + Parameters + ---------- + id: str + The model OCID. + inference_container: str. + The inference container family name + enable_finetuning: str + Flag to enable or disable finetuning over the model. Defaults to None + + Returns + ------- + Model: + The instance of oci.data_science.models.Model. + + """ + ds_model=DataScienceModel.from_id(id) + if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None): + if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG,None): + raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be edited.") + else: + custom_metadata_list=ds_model.custom_metadata_list + freeform_tags=ds_model.freeform_tags + if inference_container: + custom_metadata_list.add(key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, + value=inference_container, + category=MetadataCustomCategory.OTHER, + description="Deployment container mapping for SMC", + replace=True + ) + if enable_finetuning is not None: + if enable_finetuning.lower()=="true": + custom_metadata_list.add(key=ModelCustomMetadataFields.FINETUNE_CONTAINER, + value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, + category=MetadataCustomCategory.OTHER, + description="Fine-tuning container mapping for SMC", + replace=True + ) + freeform_tags.update({Tags.READY_TO_FINE_TUNE:"true"}) + elif enable_finetuning.lower()=="false": + try: + custom_metadata_list.remove(ModelCustomMetadataFields.FINETUNE_CONTAINER) + freeform_tags.pop(Tags.READY_TO_FINE_TUNE) + except Exception as ex: + raise AquaRuntimeError(f"The given model already doesn't support finetuning: {ex}") + + custom_metadata_list.remove("modelDescription") + if task: + freeform_tags.update({"task":task}) + + updated_custom_metadata_list = [ + Metadata(**metadata) + for metadata in custom_metadata_list.to_dict()["data"] + ] + update_model_details = UpdateModelDetails( + custom_metadata_list=updated_custom_metadata_list, + freeform_tags=freeform_tags + ) + return self.ds_client.update_model(id,update_model_details).data + else: + raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be deleted.") + + def _fetch_metric_from_metadata( self, custom_metadata_list: ModelCustomMetadata, From 5688652a859b7a745b5c1f5da5d7acac84faa5ea Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 18:36:24 +0530 Subject: [PATCH 04/22] Addressing review comments --- ads/aqua/extension/deployment_handler.py | 35 +++--- ads/aqua/extension/model_handler.py | 42 ++++--- ads/aqua/model/model.py | 134 ++++++++++++----------- 3 files changed, 117 insertions(+), 94 deletions(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 508a2b0a5..381c24b85 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -55,29 +55,28 @@ def get(self, id=""): raise HTTPError(400, f"The request {self.request.path} is invalid.") @handle_exceptions - def delete(self,model_deployment_id): + def delete(self, model_deployment_id): return self.finish(AquaDeploymentApp().delete(model_deployment_id)) @handle_exceptions - def put(self,*args,**kwargs): - try: - input_data = self.get_json_body() - except Exception as ex: - raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex - if not input_data: - raise HTTPError(400, Errors.NO_INPUT_DATA) - - # required input parameters - model_deployment_id = input_data.get("model_deployment_id") - if not model_deployment_id: - raise HTTPError( - 400, Errors.MISSING_REQUIRED_PARAMETER.format("model_deployment_id") - ) + def put(self, *args, **kwargs): + """ + Handles put request for the activating and deactivating OCI datascience model deployments + Raises + ------ + HTTPError + Raises HTTPError if inputs are missing or are invalid + """ url_parse = urlparse(self.request.path) - paths = url_parse.path.strip("/") - if paths.startswith("aqua/deployments/activate"): + paths = url_parse.path.strip("/").split("/") + if len(paths) != 4 or paths[0] != "aqua" or paths[1] != "deployments": + raise HTTPError(400, f"The request {self.request.path} is invalid.") + + model_deployment_id = paths[2] + action = paths[3] + if action == "activate": return self.finish(AquaDeploymentApp().activate(model_deployment_id)) - elif paths.startswith("aqua/deployments/deactivate"): + elif action == "deactivate": return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 49f30457a..279ea412b 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -8,14 +8,17 @@ from tornado.web import HTTPError from ads.aqua.common.decorator import handle_exceptions -from ads.aqua.common.enums import InferenceContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError -from ads.aqua.common.utils import get_hf_model_info, list_hf_models +from ads.aqua.common.utils import ( + get_container_config, + get_hf_model_info, + list_hf_models, +) from ads.aqua.extension.base_handler import AquaAPIhandler from ads.aqua.extension.errors import Errors from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModelSummary, HFModelSummary -from ads.aqua.ui import ModelFormat +from ads.aqua.ui import AquaContainerConfig, ModelFormat class AquaModelHandler(AquaAPIhandler): @@ -75,7 +78,7 @@ def delete(self, id=""): if paths.startswith("aqua/model/cache"): return self.finish(AquaModelApp().clear_model_list_cache()) elif id: - return self.finish(AquaModelApp().delete_registered_model(id)) + return self.finish(AquaModelApp().delete_model(id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") @@ -141,7 +144,7 @@ def post(self, *args, **kwargs): ) @handle_exceptions - def put(self,id): + def put(self, id): try: input_data = self.get_json_body() except Exception as ex: @@ -150,17 +153,26 @@ def put(self,id): if not input_data: raise HTTPError(400, Errors.NO_INPUT_DATA) - inference_container=input_data.get('inference_container') - if inference_container is not None and inference_container not in [ - InferenceContainerTypeFamily.AQUA_TGI_CONTAINER_FAMILY, - InferenceContainerTypeFamily.AQUA_VLLM_CONTAINER_FAMILY, - InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY - ]: - raise HTTPError(400,Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container")) - enable_finetuning=input_data.get('enable_finetuning') - task=input_data.get('task') - return self.finish(AquaModelApp().edit_registered_model(id,inference_container,enable_finetuning,task)) + inference_container = input_data.get("inference_container") + containers = list( + AquaContainerConfig.from_container_index_json( + config=get_container_config(), enable_spec=True + ).inference.values() + ) + family_values = [item.family for item in containers] + + if inference_container is not None and inference_container not in family_values: + raise HTTPError( + 400, Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container") + ) + enable_finetuning = input_data.get("enable_finetuning") + task = input_data.get("task") + return self.finish( + AquaModelApp().edit_registered_model( + id, inference_container, enable_finetuning, task + ) + ) class AquaModelLicenseHandler(AquaAPIhandler): diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 27883e043..aef18fc88 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -10,11 +10,15 @@ import oci from cachetools import TTLCache from huggingface_hub import snapshot_download -from oci.data_science.models import JobRun, Model, UpdateModelDetails, Metadata +from oci.data_science.models import JobRun, Metadata, Model, UpdateModelDetails from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger from ads.aqua.app import AquaApp -from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags, FineTuningContainerTypeFamily +from ads.aqua.common.enums import ( + FineTuningContainerTypeFamily, + InferenceContainerTypeFamily, + Tags, +) from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( LifecycleStatus, @@ -75,7 +79,11 @@ TENANCY_OCID, ) from ads.model import DataScienceModel -from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem, MetadataCustomCategory +from ads.model.model_metadata import ( + MetadataCustomCategory, + ModelCustomMetadata, + ModelCustomMetadataItem, +) from ads.telemetry import telemetry @@ -324,16 +332,21 @@ def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaMod return model_details @telemetry(entry_point="plugin=model&action=delete", name="aqua") - def delete_registered_model(self,model_id): - ds_model=DataScienceModel.from_id(model_id) - is_registered_model=ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None) - if is_registered_model: + def delete_model(self, model_id): + ds_model = DataScienceModel.from_id(model_id) + is_registered_model = ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None) + is_fine_tuned_model = ds_model.freeform_tags.get( + Tags.AQUA_FINE_TUNED_MODEL_TAG, None + ) + if is_registered_model or is_fine_tuned_model: return ds_model.delete() else: - raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + raise AquaRuntimeError( + f"Failed to delete model:{model_id}. Only registered models or finetuned model can be deleted." + ) @telemetry(entry_point="plugin=model&action=delete", name="aqua") - def edit_registered_model(self,id,inference_container,enable_finetuning,task): + def edit_registered_model(self, id, inference_container, enable_finetuning, task): """Edits the default config of unverified registered model. Parameters @@ -351,39 +364,47 @@ def edit_registered_model(self,id,inference_container,enable_finetuning,task): The instance of oci.data_science.models.Model. """ - ds_model=DataScienceModel.from_id(id) - if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None): - if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG,None): - raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be edited.") + ds_model = DataScienceModel.from_id(id) + if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None): + if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG, None): + raise AquaRuntimeError( + f"Failed to edit model:{id}. Only registered unverified models can be edited." + ) else: - custom_metadata_list=ds_model.custom_metadata_list - freeform_tags=ds_model.freeform_tags + custom_metadata_list = ds_model.custom_metadata_list + freeform_tags = ds_model.freeform_tags if inference_container: - custom_metadata_list.add(key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, - value=inference_container, - category=MetadataCustomCategory.OTHER, - description="Deployment container mapping for SMC", - replace=True - ) + custom_metadata_list.add( + key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, + value=inference_container, + category=MetadataCustomCategory.OTHER, + description="Deployment container mapping for SMC", + replace=True, + ) if enable_finetuning is not None: - if enable_finetuning.lower()=="true": - custom_metadata_list.add(key=ModelCustomMetadataFields.FINETUNE_CONTAINER, - value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, - category=MetadataCustomCategory.OTHER, - description="Fine-tuning container mapping for SMC", - replace=True - ) - freeform_tags.update({Tags.READY_TO_FINE_TUNE:"true"}) - elif enable_finetuning.lower()=="false": + if enable_finetuning.lower() == "true": + custom_metadata_list.add( + key=ModelCustomMetadataFields.FINETUNE_CONTAINER, + value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, + category=MetadataCustomCategory.OTHER, + description="Fine-tuning container mapping for SMC", + replace=True, + ) + freeform_tags.update({Tags.READY_TO_FINE_TUNE: "true"}) + elif enable_finetuning.lower() == "false": try: - custom_metadata_list.remove(ModelCustomMetadataFields.FINETUNE_CONTAINER) + custom_metadata_list.remove( + ModelCustomMetadataFields.FINETUNE_CONTAINER + ) freeform_tags.pop(Tags.READY_TO_FINE_TUNE) except Exception as ex: - raise AquaRuntimeError(f"The given model already doesn't support finetuning: {ex}") + raise AquaRuntimeError( + f"The given model already doesn't support finetuning: {ex}" + ) custom_metadata_list.remove("modelDescription") if task: - freeform_tags.update({"task":task}) + freeform_tags.update({"task": task}) updated_custom_metadata_list = [ Metadata(**metadata) @@ -391,12 +412,13 @@ def edit_registered_model(self,id,inference_container,enable_finetuning,task): ] update_model_details = UpdateModelDetails( custom_metadata_list=updated_custom_metadata_list, - freeform_tags=freeform_tags + freeform_tags=freeform_tags, ) - return self.ds_client.update_model(id,update_model_details).data + return AquaApp().update_model(id, update_model_details).data else: - raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be deleted.") - + raise AquaRuntimeError( + f"Failed to edit model:{id}. Only registered unverified models can be deleted." + ) def _fetch_metric_from_metadata( self, @@ -1010,14 +1032,15 @@ def _validate_model( # gguf extension exist. if {ModelFormat.SAFETENSORS, ModelFormat.GGUF}.issubset(set(model_formats)): if ( - import_model_details.inference_container.lower() == InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY + import_model_details.inference_container.lower() + == InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY ): self._validate_gguf_format( import_model_details=import_model_details, verified_model=verified_model, gguf_model_files=gguf_model_files, validation_result=validation_result, - model_name=model_name + model_name=model_name, ) else: self._validate_safetensor_format( @@ -1025,7 +1048,7 @@ def _validate_model( verified_model=verified_model, validation_result=validation_result, hf_download_config_present=hf_download_config_present, - model_name=model_name + model_name=model_name, ) elif ModelFormat.SAFETENSORS in model_formats: self._validate_safetensor_format( @@ -1033,7 +1056,7 @@ def _validate_model( verified_model=verified_model, validation_result=validation_result, hf_download_config_present=hf_download_config_present, - model_name=model_name + model_name=model_name, ) elif ModelFormat.GGUF in model_formats: self._validate_gguf_format( @@ -1041,7 +1064,7 @@ def _validate_model( verified_model=verified_model, gguf_model_files=gguf_model_files, validation_result=validation_result, - model_name=model_name + model_name=model_name, ) return validation_result @@ -1052,7 +1075,7 @@ def _validate_safetensor_format( verified_model: DataScienceModel = None, validation_result: ModelValidationResult = None, hf_download_config_present: bool = None, - model_name: str = None + model_name: str = None, ): if import_model_details.download_from_hf: # validates config.json exists for safetensors model from hugginface @@ -1079,20 +1102,13 @@ def _validate_safetensor_format( ) from ex else: try: - metadata_model_type = ( - verified_model.custom_metadata_list.get( - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - ).value - ) + metadata_model_type = verified_model.custom_metadata_list.get( + AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE + ).value if metadata_model_type: - if ( - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - in model_config - ): + if AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE in model_config: if ( - model_config[ - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - ] + model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE] != metadata_model_type ): raise AquaRuntimeError( @@ -1110,9 +1126,7 @@ def _validate_safetensor_format( except Exception: pass if verified_model: - validation_result.telemetry_model_name = ( - verified_model.display_name - ) + validation_result.telemetry_model_name = verified_model.display_name elif ( model_config is not None and AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME in model_config @@ -1124,9 +1138,7 @@ def _validate_safetensor_format( ): validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE]}" else: - validation_result.telemetry_model_name = ( - AQUA_MODEL_TYPE_CUSTOM - ) + validation_result.telemetry_model_name = AQUA_MODEL_TYPE_CUSTOM @staticmethod def _validate_gguf_format( From fb60ed0b79ef8b470b00c6381e2915af51c06956 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 19:21:45 +0530 Subject: [PATCH 05/22] Adding review comments --- ads/aqua/extension/deployment_handler.py | 2 ++ ads/aqua/model/model.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 381c24b85..72df4200f 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -291,5 +291,7 @@ def post(self, *args, **kwargs): ("deployments/?([^/]*)/params", AquaDeploymentParamsHandler), ("deployments/config/?([^/]*)", AquaDeploymentHandler), ("deployments/?([^/]*)", AquaDeploymentHandler), + ("deployments/?([^/]*)/activate", AquaDeploymentHandler), + ("deployments/?([^/]*)/deactivate", AquaDeploymentHandler), ("inference", AquaDeploymentInferenceHandler), ] diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index aef18fc88..402b63432 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -404,7 +404,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task custom_metadata_list.remove("modelDescription") if task: - freeform_tags.update({"task": task}) + freeform_tags.update({Tags.TASK: task}) updated_custom_metadata_list = [ Metadata(**metadata) From e1b985f8e19ab57b3251b232daeb60983e68c55d Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 19:27:20 +0530 Subject: [PATCH 06/22] Adding review comments --- ads/aqua/model/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 402b63432..03e4b595e 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -357,6 +357,8 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task The inference container family name enable_finetuning: str Flag to enable or disable finetuning over the model. Defaults to None + task: + The usecase type of the model. e.g , text-generation , text_embedding etc. Returns ------- From 10d06ccb804b47b847a9745e502e834910e83fb5 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 16:45:58 +0530 Subject: [PATCH 07/22] Adding UT for delete model --- .../with_extras/aqua/test_model_handler.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index cb7a27080..f5015763a 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -19,7 +19,6 @@ AquaModelLicenseHandler, ) from ads.aqua.model import AquaModelApp -from ads.aqua.model.constants import ModelTask from ads.aqua.model.entities import AquaModel, AquaModelSummary, HFModelSummary @@ -79,6 +78,21 @@ def test_delete(self, mock_urlparse, mock_clear_model_list_cache): mock_urlparse.assert_called() mock_clear_model_list_cache.assert_called() + @patch("ads.aqua.extension.model_handler.urlparse") + @patch.object(AquaModelApp, "delete_model") + def test_delete_with_id(self, mock_delete, mock_urlparse): + request_path = MagicMock(path="aqua/model/ocid1.datasciencemodel.oc1.iad.xxx") + mock_urlparse.return_value = request_path + mock_delete.return_value = {"state": "DELETED"} + with patch( + "ads.aqua.extension.base_handler.AquaAPIhandler.finish" + ) as mock_finish: + mock_finish.side_effect = lambda x: x + result = self.model_handler.delete(id="ocid1.datasciencemodel.oc1.iad.xxx") + assert result["state"] is "DELETED" + mock_urlparse.assert_called() + mock_delete.assert_called() + @patch.object(AquaModelApp, "list") def test_list(self, mock_list): with patch( From 5baf623d3a46a220702ecb938136cd53a6ab3b6c Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 21:55:04 +0530 Subject: [PATCH 08/22] Adding UTs for model deployment --- .../aqua/test_deployment_handler.py | 20 ++++++++++++++ .../with_extras/aqua/test_model_handler.py | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/unitary/with_extras/aqua/test_deployment_handler.py b/tests/unitary/with_extras/aqua/test_deployment_handler.py index 54756545a..75999fade 100644 --- a/tests/unitary/with_extras/aqua/test_deployment_handler.py +++ b/tests/unitary/with_extras/aqua/test_deployment_handler.py @@ -92,6 +92,26 @@ def test_get_deployment(self, mock_get): self.deployment_handler.get(id="mock-model-id") mock_get.assert_called() + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.delete") + def test_delete_deployment(self,mock_delete): + self.deployment_handler.request.path = "aqua/deployments" + self.deployment_handler.delete("mock-model-id") + mock_delete.assert_called() + + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.activate") + def test_activate_deployment(self,mock_activate): + self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" + mock_activate.return_value={"lifecycle_state":"UPDATING"} + self.deployment_handler.put() + mock_activate.assert_called() + + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.deactivate") + def test_deactivate_deployment(self,mock_deactivate): + self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" + mock_deactivate.return_value={"lifecycle_state":"UPDATING"} + self.deployment_handler.put() + mock_deactivate.assert_called() + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.list") def test_list_deployment(self, mock_list): """Test get method to return a list of model deployments.""" diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index f5015763a..58148fc95 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -20,6 +20,7 @@ ) from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModel, AquaModelSummary, HFModelSummary +from ads.aqua.ui import AquaContainerConfig class ModelHandlerTestCase(TestCase): @@ -93,6 +94,32 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): mock_urlparse.assert_called() mock_delete.assert_called() + @patch.object(AquaContainerConfig,"from_container_index_json") + @patch.object(AquaModelApp,"edit_registered_model") + def test_put(self,mock_edit,mock_container_index): + mock_edit.return_value={"state":"EDITED"} + mock_inference = MagicMock() + mock_inference.values.return_value = [ + MagicMock(family="odsc-vllm-serving"), + MagicMock(family="odsc-tgi-serving"), + MagicMock(family="odsc-vllm-serving"), + ] + + mock_container_index.return_value = MagicMock(inference=mock_inference) + self.model_handler.get_json_body = MagicMock( + return_value=dict( + task="text_generation", + enable_finetuning="true", + inference_container="odsc-tgi-serving", + ) + ) + with patch("ads.aqua.extension.base_handler.AquaAPIhandler.finish") as mock_finish: + mock_finish.side_effect = lambda x: x + result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") + print(f"result: ",result) + assert result["state"] is "EDITED" + mock_edit.assert_called() + @patch.object(AquaModelApp, "list") def test_list(self, mock_list): with patch( From 3582437e5bdead848a46a9b5ee4e2e809cc46eaa Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 22:02:10 +0530 Subject: [PATCH 09/22] formatting --- ads/aqua/model/model.py | 2 +- .../aqua/test_deployment_handler.py | 18 +++++++++++------- .../with_extras/aqua/test_model_handler.py | 14 ++++++++------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 03e4b595e..d07c4ecab 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -419,7 +419,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task return AquaApp().update_model(id, update_model_details).data else: raise AquaRuntimeError( - f"Failed to edit model:{id}. Only registered unverified models can be deleted." + f"Failed to edit model:{id}. Only registered unverified models can be edited." ) def _fetch_metric_from_metadata( diff --git a/tests/unitary/with_extras/aqua/test_deployment_handler.py b/tests/unitary/with_extras/aqua/test_deployment_handler.py index 75999fade..a3c843113 100644 --- a/tests/unitary/with_extras/aqua/test_deployment_handler.py +++ b/tests/unitary/with_extras/aqua/test_deployment_handler.py @@ -93,22 +93,26 @@ def test_get_deployment(self, mock_get): mock_get.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.delete") - def test_delete_deployment(self,mock_delete): + def test_delete_deployment(self, mock_delete): self.deployment_handler.request.path = "aqua/deployments" self.deployment_handler.delete("mock-model-id") mock_delete.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.activate") - def test_activate_deployment(self,mock_activate): - self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" - mock_activate.return_value={"lifecycle_state":"UPDATING"} + def test_activate_deployment(self, mock_activate): + self.deployment_handler.request.path = ( + "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" + ) + mock_activate.return_value = {"lifecycle_state": "UPDATING"} self.deployment_handler.put() mock_activate.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.deactivate") - def test_deactivate_deployment(self,mock_deactivate): - self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" - mock_deactivate.return_value={"lifecycle_state":"UPDATING"} + def test_deactivate_deployment(self, mock_deactivate): + self.deployment_handler.request.path = ( + "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" + ) + mock_deactivate.return_value = {"lifecycle_state": "UPDATING"} self.deployment_handler.put() mock_deactivate.assert_called() diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index 58148fc95..db5475798 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -94,10 +94,10 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): mock_urlparse.assert_called() mock_delete.assert_called() - @patch.object(AquaContainerConfig,"from_container_index_json") - @patch.object(AquaModelApp,"edit_registered_model") - def test_put(self,mock_edit,mock_container_index): - mock_edit.return_value={"state":"EDITED"} + @patch.object(AquaContainerConfig, "from_container_index_json") + @patch.object(AquaModelApp, "edit_registered_model") + def test_put(self, mock_edit, mock_container_index): + mock_edit.return_value = {"state": "EDITED"} mock_inference = MagicMock() mock_inference.values.return_value = [ MagicMock(family="odsc-vllm-serving"), @@ -113,10 +113,12 @@ def test_put(self,mock_edit,mock_container_index): inference_container="odsc-tgi-serving", ) ) - with patch("ads.aqua.extension.base_handler.AquaAPIhandler.finish") as mock_finish: + with patch( + "ads.aqua.extension.base_handler.AquaAPIhandler.finish" + ) as mock_finish: mock_finish.side_effect = lambda x: x result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") - print(f"result: ",result) + print(f"result: ", result) assert result["state"] is "EDITED" mock_edit.assert_called() From 76be46c39f968536f67c980c39c7ec268d357108 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 10:54:29 +0530 Subject: [PATCH 10/22] Rebasing and fixing UTs --- ads/aqua/extension/model_handler.py | 16 ++++++--------- ads/aqua/model/model.py | 11 ++++++++++ .../with_extras/aqua/test_model_handler.py | 20 ++++++++----------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 279ea412b..c62f1f79d 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -10,7 +10,6 @@ from ads.aqua.common.decorator import handle_exceptions from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( - get_container_config, get_hf_model_info, list_hf_models, ) @@ -18,7 +17,7 @@ from ads.aqua.extension.errors import Errors from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModelSummary, HFModelSummary -from ads.aqua.ui import AquaContainerConfig, ModelFormat +from ads.aqua.ui import ModelFormat class AquaModelHandler(AquaAPIhandler): @@ -154,14 +153,11 @@ def put(self, id): raise HTTPError(400, Errors.NO_INPUT_DATA) inference_container = input_data.get("inference_container") - containers = list( - AquaContainerConfig.from_container_index_json( - config=get_container_config(), enable_spec=True - ).inference.values() - ) - family_values = [item.family for item in containers] - - if inference_container is not None and inference_container not in family_values: + inference_containers = AquaModelApp.list_valid_inference_containers() + if ( + inference_container is not None + and inference_container not in inference_containers + ): raise HTTPError( 400, Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container") ) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index d07c4ecab..4aabedded 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -26,6 +26,7 @@ copy_model_config, create_word_icon, get_artifact_path, + get_container_config, get_hf_model_info, list_os_files_with_extension, load_config, @@ -718,6 +719,16 @@ def clear_model_list_cache( } return res + @staticmethod + def list_valid_inference_containers(): + containers = list( + AquaContainerConfig.from_container_index_json( + config=get_container_config(), enable_spec=True + ).inference.values() + ) + family_values = [item.family for item in containers] + return family_values + def _create_model_catalog_entry( self, os_path: str, diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index db5475798..1e165dcbd 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -20,7 +20,6 @@ ) from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModel, AquaModelSummary, HFModelSummary -from ads.aqua.ui import AquaContainerConfig class ModelHandlerTestCase(TestCase): @@ -94,18 +93,15 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): mock_urlparse.assert_called() mock_delete.assert_called() - @patch.object(AquaContainerConfig, "from_container_index_json") + @patch.object(AquaModelApp, "list_valid_inference_containers") @patch.object(AquaModelApp, "edit_registered_model") - def test_put(self, mock_edit, mock_container_index): + def test_put(self, mock_edit, mock_inference_container_list): mock_edit.return_value = {"state": "EDITED"} - mock_inference = MagicMock() - mock_inference.values.return_value = [ - MagicMock(family="odsc-vllm-serving"), - MagicMock(family="odsc-tgi-serving"), - MagicMock(family="odsc-vllm-serving"), + mock_inference_container_list.return_value = [ + "odsc-vllm-serving", + "odsc-tgi-serving", + "odsc-llama-cpp-serving", ] - - mock_container_index.return_value = MagicMock(inference=mock_inference) self.model_handler.get_json_body = MagicMock( return_value=dict( task="text_generation", @@ -118,9 +114,9 @@ def test_put(self, mock_edit, mock_container_index): ) as mock_finish: mock_finish.side_effect = lambda x: x result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") - print(f"result: ", result) assert result["state"] is "EDITED" - mock_edit.assert_called() + mock_edit.assert_called_once() + mock_inference_container_list.assert_called_once() @patch.object(AquaModelApp, "list") def test_list(self, mock_list): From e0f0f3b325f742c24fba302e4cde032095a8deb7 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 18:44:21 +0530 Subject: [PATCH 11/22] Adding task for unverified model import --- ads/aqua/config/config.py | 8 ++++++++ ads/aqua/extension/model_handler.py | 8 +++++++- ads/aqua/model/entities.py | 1 + ads/aqua/model/model.py | 3 +++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ads/aqua/config/config.py b/ads/aqua/config/config.py index 22cf9f27d..1802389f9 100644 --- a/ads/aqua/config/config.py +++ b/ads/aqua/config/config.py @@ -29,3 +29,11 @@ def get_evaluation_service_config( .get(ContainerSpec.CONTAINER_SPEC, {}) .get(container, {}) ) + +def get_valid_tasks(): + return [ + "text_generation", + "code_synthesis", + "image_text_to_text", + "feature_extraction", + ] diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index c62f1f79d..47a6b353a 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -13,6 +13,7 @@ get_hf_model_info, list_hf_models, ) +from ads.aqua.config.config import get_valid_tasks from ads.aqua.extension.base_handler import AquaAPIhandler from ads.aqua.extension.errors import Errors from ads.aqua.model import AquaModelApp @@ -119,6 +120,9 @@ def post(self, *args, **kwargs): os_path = input_data.get("os_path") if not os_path: raise HTTPError(400, Errors.MISSING_REQUIRED_PARAMETER.format("os_path")) + task = input_data.get("task") + if task not in get_valid_tasks(): + raise HTTPError(400, Errors.INVALID_VALUE_OF_PARAMETER.format("task")) inference_container = input_data.get("inference_container") finetuning_container = input_data.get("finetuning_container") @@ -128,7 +132,6 @@ def post(self, *args, **kwargs): download_from_hf = ( str(input_data.get("download_from_hf", "false")).lower() == "true" ) - return self.finish( AquaModelApp().register( model=model, @@ -139,6 +142,7 @@ def post(self, *args, **kwargs): compartment_id=compartment_id, project_id=project_id, model_file=model_file, + task=task, ) ) @@ -164,6 +168,8 @@ def put(self, id): enable_finetuning = input_data.get("enable_finetuning") task = input_data.get("task") + if task not in get_valid_tasks(): + raise HTTPError(400, Errors.INVALID_VALUE_OF_PARAMETER.format("task")) return self.finish( AquaModelApp().edit_registered_model( id, inference_container, enable_finetuning, task diff --git a/ads/aqua/model/entities.py b/ads/aqua/model/entities.py index 31125644f..41cc9e44f 100644 --- a/ads/aqua/model/entities.py +++ b/ads/aqua/model/entities.py @@ -284,6 +284,7 @@ class ImportModelDetails(CLIBuilderMixin): local_dir: Optional[str] = None inference_container: Optional[str] = None finetuning_container: Optional[str] = None + task: Optional[str] = None compartment_id: Optional[str] = None project_id: Optional[str] = None model_file: Optional[str] = None diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 4aabedded..2a41a65d0 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -735,6 +735,7 @@ def _create_model_catalog_entry( model_name: str, inference_container: str, finetuning_container: str, + task: Optional[str], verified_model: DataScienceModel, validation_result: ModelValidationResult, compartment_id: Optional[str], @@ -790,6 +791,7 @@ def _create_model_catalog_entry( json_dict=verified_model.model_file_description ) else: + tags.update({Tags.TASK: task}) metadata = ModelCustomMetadata() if not inference_container: raise AquaRuntimeError( @@ -1336,6 +1338,7 @@ def register( model_name=model_name, inference_container=import_model_details.inference_container, finetuning_container=import_model_details.finetuning_container, + task=import_model_details.task, verified_model=verified_model, validation_result=validation_result, compartment_id=import_model_details.compartment_id, From 27023762da6657641e97cc92ea94463bf9e65086 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 19:23:12 +0530 Subject: [PATCH 12/22] Updating put method for register model --- ads/aqua/extension/model_handler.py | 2 +- ads/aqua/model/model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 47a6b353a..3cf288bbd 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -170,7 +170,7 @@ def put(self, id): task = input_data.get("task") if task not in get_valid_tasks(): raise HTTPError(400, Errors.INVALID_VALUE_OF_PARAMETER.format("task")) - return self.finish( + self.finish( AquaModelApp().edit_registered_model( id, inference_container, enable_finetuning, task ) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 2a41a65d0..25f95afbe 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -417,7 +417,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task custom_metadata_list=updated_custom_metadata_list, freeform_tags=freeform_tags, ) - return AquaApp().update_model(id, update_model_details).data + AquaApp().update_model(id, update_model_details) else: raise AquaRuntimeError( f"Failed to edit model:{id}. Only registered unverified models can be edited." From 787c0670ff4d340806db4cad04f3325fee0d300c Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 20:39:11 +0530 Subject: [PATCH 13/22] adding task validation --- ads/aqua/extension/model_handler.py | 4 ---- ads/aqua/model/entities.py | 1 - ads/aqua/model/model.py | 10 +++++----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 3cf288bbd..49236b31e 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -120,9 +120,6 @@ def post(self, *args, **kwargs): os_path = input_data.get("os_path") if not os_path: raise HTTPError(400, Errors.MISSING_REQUIRED_PARAMETER.format("os_path")) - task = input_data.get("task") - if task not in get_valid_tasks(): - raise HTTPError(400, Errors.INVALID_VALUE_OF_PARAMETER.format("task")) inference_container = input_data.get("inference_container") finetuning_container = input_data.get("finetuning_container") @@ -142,7 +139,6 @@ def post(self, *args, **kwargs): compartment_id=compartment_id, project_id=project_id, model_file=model_file, - task=task, ) ) diff --git a/ads/aqua/model/entities.py b/ads/aqua/model/entities.py index 41cc9e44f..31125644f 100644 --- a/ads/aqua/model/entities.py +++ b/ads/aqua/model/entities.py @@ -284,7 +284,6 @@ class ImportModelDetails(CLIBuilderMixin): local_dir: Optional[str] = None inference_container: Optional[str] = None finetuning_container: Optional[str] = None - task: Optional[str] = None compartment_id: Optional[str] = None project_id: Optional[str] = None model_file: Optional[str] = None diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 25f95afbe..7efd09ac5 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -33,6 +33,7 @@ read_file, upload_folder, ) +from ads.aqua.config.config import get_valid_tasks from ads.aqua.constants import ( AQUA_MODEL_ARTIFACT_CONFIG, AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME, @@ -735,7 +736,6 @@ def _create_model_catalog_entry( model_name: str, inference_container: str, finetuning_container: str, - task: Optional[str], verified_model: DataScienceModel, validation_result: ModelValidationResult, compartment_id: Optional[str], @@ -768,7 +768,7 @@ def _create_model_catalog_entry( } ) tags.update({Tags.BASE_MODEL_CUSTOM: "true"}) - + logger.info(f"tags: {tags}") if validation_result and validation_result.model_formats: tags.update( { @@ -781,7 +781,7 @@ def _create_model_catalog_entry( # Remove `ready_to_import` tag that might get copied from service model. tags.pop(Tags.READY_TO_IMPORT, None) - + logger.info(f"tags: {tags}") if verified_model: # Verified model is a model in the service catalog that either has no artifacts but contains all the necessary metadata for deploying and fine tuning. # If set, then we copy all the model metadata. @@ -791,7 +791,6 @@ def _create_model_catalog_entry( json_dict=verified_model.model_file_description ) else: - tags.update({Tags.TASK: task}) metadata = ModelCustomMetadata() if not inference_container: raise AquaRuntimeError( @@ -864,6 +863,7 @@ def _create_model_catalog_entry( category="Other", replace=True, ) + logger.info(f"tags: {tags}") model = ( model.with_custom_metadata_list(metadata) .with_compartment_id(compartment_id or COMPARTMENT_OCID) @@ -1332,13 +1332,13 @@ def register( else: artifact_path = import_model_details.os_path.rstrip("/") + logger.info(f"task: {import_model_details.task}") # Create Model catalog entry with pass by reference ds_model = self._create_model_catalog_entry( os_path=artifact_path, model_name=model_name, inference_container=import_model_details.inference_container, finetuning_container=import_model_details.finetuning_container, - task=import_model_details.task, verified_model=verified_model, validation_result=validation_result, compartment_id=import_model_details.compartment_id, From 5ae67165c74fd09b8cb658ae75e76f46f097030e Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 20:43:11 +0530 Subject: [PATCH 14/22] Removing log info statements --- ads/aqua/model/model.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 7efd09ac5..dbb07f9cd 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -33,7 +33,6 @@ read_file, upload_folder, ) -from ads.aqua.config.config import get_valid_tasks from ads.aqua.constants import ( AQUA_MODEL_ARTIFACT_CONFIG, AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME, @@ -768,7 +767,6 @@ def _create_model_catalog_entry( } ) tags.update({Tags.BASE_MODEL_CUSTOM: "true"}) - logger.info(f"tags: {tags}") if validation_result and validation_result.model_formats: tags.update( { @@ -781,7 +779,6 @@ def _create_model_catalog_entry( # Remove `ready_to_import` tag that might get copied from service model. tags.pop(Tags.READY_TO_IMPORT, None) - logger.info(f"tags: {tags}") if verified_model: # Verified model is a model in the service catalog that either has no artifacts but contains all the necessary metadata for deploying and fine tuning. # If set, then we copy all the model metadata. @@ -863,7 +860,6 @@ def _create_model_catalog_entry( category="Other", replace=True, ) - logger.info(f"tags: {tags}") model = ( model.with_custom_metadata_list(metadata) .with_compartment_id(compartment_id or COMPARTMENT_OCID) @@ -1332,7 +1328,6 @@ def register( else: artifact_path = import_model_details.os_path.rstrip("/") - logger.info(f"task: {import_model_details.task}") # Create Model catalog entry with pass by reference ds_model = self._create_model_catalog_entry( os_path=artifact_path, From b88ab6c4415516f1fa0902abcf52e8cae92cc8be Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 20:45:27 +0530 Subject: [PATCH 15/22] formatting --- ads/aqua/extension/model_handler.py | 1 + ads/aqua/model/model.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 49236b31e..746f44f04 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -129,6 +129,7 @@ def post(self, *args, **kwargs): download_from_hf = ( str(input_data.get("download_from_hf", "false")).lower() == "true" ) + return self.finish( AquaModelApp().register( model=model, diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index dbb07f9cd..a1bc76021 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -767,6 +767,7 @@ def _create_model_catalog_entry( } ) tags.update({Tags.BASE_MODEL_CUSTOM: "true"}) + if validation_result and validation_result.model_formats: tags.update( { @@ -779,6 +780,7 @@ def _create_model_catalog_entry( # Remove `ready_to_import` tag that might get copied from service model. tags.pop(Tags.READY_TO_IMPORT, None) + if verified_model: # Verified model is a model in the service catalog that either has no artifacts but contains all the necessary metadata for deploying and fine tuning. # If set, then we copy all the model metadata. From a2f57f77f9cf6e85d07f455567f65aa1217f7408 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 20:59:20 +0530 Subject: [PATCH 16/22] Fixing UTs --- tests/unitary/with_extras/aqua/test_model_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index 1e165dcbd..9cf054c64 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -96,7 +96,7 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): @patch.object(AquaModelApp, "list_valid_inference_containers") @patch.object(AquaModelApp, "edit_registered_model") def test_put(self, mock_edit, mock_inference_container_list): - mock_edit.return_value = {"state": "EDITED"} + mock_edit.return_value = None mock_inference_container_list.return_value = [ "odsc-vllm-serving", "odsc-tgi-serving", @@ -114,7 +114,7 @@ def test_put(self, mock_edit, mock_inference_container_list): ) as mock_finish: mock_finish.side_effect = lambda x: x result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") - assert result["state"] is "EDITED" + assert result is None mock_edit.assert_called_once() mock_inference_container_list.assert_called_once() From 5a71b0bd5894bd677986d616c8af0d90e2d6b43e Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 21:15:07 +0530 Subject: [PATCH 17/22] Adding validation for cli --- ads/aqua/model/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index a1bc76021..f13079410 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -33,6 +33,7 @@ read_file, upload_folder, ) +from ads.aqua.config.config import get_valid_tasks from ads.aqua.constants import ( AQUA_MODEL_ARTIFACT_CONFIG, AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME, @@ -407,7 +408,12 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task custom_metadata_list.remove("modelDescription") if task: - freeform_tags.update({Tags.TASK: task}) + if task in get_valid_tasks(): + freeform_tags.update({Tags.TASK: task}) + else: + raise AquaValueError( + f"Failed to edit model with the given task parameter. Acceptable values are: {get_valid_tasks()}" + ) updated_custom_metadata_list = [ Metadata(**metadata) From a941aefefc8a2c099badb30c955113a4f66f20be Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 23:55:25 +0530 Subject: [PATCH 18/22] Removing validation for task --- ads/aqua/config/config.py | 7 ------- ads/aqua/extension/model_handler.py | 3 --- ads/aqua/model/model.py | 9 +-------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/ads/aqua/config/config.py b/ads/aqua/config/config.py index 1802389f9..3ad30138a 100644 --- a/ads/aqua/config/config.py +++ b/ads/aqua/config/config.py @@ -30,10 +30,3 @@ def get_evaluation_service_config( .get(container, {}) ) -def get_valid_tasks(): - return [ - "text_generation", - "code_synthesis", - "image_text_to_text", - "feature_extraction", - ] diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 746f44f04..b6832a2d3 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -13,7 +13,6 @@ get_hf_model_info, list_hf_models, ) -from ads.aqua.config.config import get_valid_tasks from ads.aqua.extension.base_handler import AquaAPIhandler from ads.aqua.extension.errors import Errors from ads.aqua.model import AquaModelApp @@ -165,8 +164,6 @@ def put(self, id): enable_finetuning = input_data.get("enable_finetuning") task = input_data.get("task") - if task not in get_valid_tasks(): - raise HTTPError(400, Errors.INVALID_VALUE_OF_PARAMETER.format("task")) self.finish( AquaModelApp().edit_registered_model( id, inference_container, enable_finetuning, task diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index f13079410..3fa0c9e0f 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -33,7 +33,6 @@ read_file, upload_folder, ) -from ads.aqua.config.config import get_valid_tasks from ads.aqua.constants import ( AQUA_MODEL_ARTIFACT_CONFIG, AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME, @@ -408,13 +407,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task custom_metadata_list.remove("modelDescription") if task: - if task in get_valid_tasks(): - freeform_tags.update({Tags.TASK: task}) - else: - raise AquaValueError( - f"Failed to edit model with the given task parameter. Acceptable values are: {get_valid_tasks()}" - ) - + freeform_tags.update({Tags.TASK: task}) updated_custom_metadata_list = [ Metadata(**metadata) for metadata in custom_metadata_list.to_dict()["data"] From 17203cb5f691ab13fcbeeef708e7a6d0cd4ab62a Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 29 Oct 2024 23:56:21 +0530 Subject: [PATCH 19/22] formatting --- ads/aqua/config/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ads/aqua/config/config.py b/ads/aqua/config/config.py index 3ad30138a..22cf9f27d 100644 --- a/ads/aqua/config/config.py +++ b/ads/aqua/config/config.py @@ -29,4 +29,3 @@ def get_evaluation_service_config( .get(ContainerSpec.CONTAINER_SPEC, {}) .get(container, {}) ) - From 74c9bf3595e42e677d6d742acf492c65271d0cd7 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Thu, 31 Oct 2024 02:00:50 +0530 Subject: [PATCH 20/22] Adding delete model details cache feature --- ads/aqua/extension/model_handler.py | 1 + ads/aqua/model/model.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index b6832a2d3..6771c5c11 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -169,6 +169,7 @@ def put(self, id): id, inference_container, enable_finetuning, task ) ) + AquaModelApp().clear_model_details_cache(model_id=id) class AquaModelLicenseHandler(AquaAPIhandler): diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 3fa0c9e0f..6c31709ee 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -718,6 +718,27 @@ def clear_model_list_cache( } return res + def clear_model_details_cache(self,model_id): + """ + Allows user to clear model details cache item + Returns + ------- + dict with the key used, and True if cache has the key that needs to be deleted. + """ + res={} + logger.info("Clearing _service_model_details_cache") + with self._cache_lock: + if model_id in self._service_model_details_cache: + self._service_model_details_cache.pop(key=model_id) + res={ + "key":{ + "model_id":model_id + }, + "cache_deleted":True + } + + return res + @staticmethod def list_valid_inference_containers(): containers = list( From eaf482238f9df31683c86838207032a093fd4203 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Thu, 31 Oct 2024 02:02:07 +0530 Subject: [PATCH 21/22] Formatting --- ads/aqua/model/model.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 6c31709ee..fb7a69a4d 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -718,24 +718,19 @@ def clear_model_list_cache( } return res - def clear_model_details_cache(self,model_id): + def clear_model_details_cache(self, model_id): """ Allows user to clear model details cache item Returns ------- dict with the key used, and True if cache has the key that needs to be deleted. """ - res={} + res = {} logger.info("Clearing _service_model_details_cache") with self._cache_lock: if model_id in self._service_model_details_cache: self._service_model_details_cache.pop(key=model_id) - res={ - "key":{ - "model_id":model_id - }, - "cache_deleted":True - } + res = {"key": {"model_id": model_id}, "cache_deleted": True} return res From 838d543aabdc4790825641231e1ccab2ce3f3500 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Thu, 31 Oct 2024 02:51:51 +0530 Subject: [PATCH 22/22] Addressing review comments --- ads/aqua/extension/model_handler.py | 5 +++-- ads/aqua/model/model.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 6771c5c11..1ca349dcc 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -164,12 +164,13 @@ def put(self, id): enable_finetuning = input_data.get("enable_finetuning") task = input_data.get("task") + app=AquaModelApp() self.finish( - AquaModelApp().edit_registered_model( + app.edit_registered_model( id, inference_container, enable_finetuning, task ) ) - AquaModelApp().clear_model_details_cache(model_id=id) + app.clear_model_details_cache(model_id=id) class AquaModelLicenseHandler(AquaAPIhandler): diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index fb7a69a4d..c9a110a77 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -726,7 +726,7 @@ def clear_model_details_cache(self, model_id): dict with the key used, and True if cache has the key that needs to be deleted. """ res = {} - logger.info("Clearing _service_model_details_cache") + logger.info(f"Clearing _service_model_details_cache for {model_id}") with self._cache_lock: if model_id in self._service_model_details_cache: self._service_model_details_cache.pop(key=model_id)