diff --git a/ads/aqua/config/config.py b/ads/aqua/config/config.py index 1ccf2c703..22cf9f27d 100644 --- a/ads/aqua/config/config.py +++ b/ads/aqua/config/config.py @@ -29,19 +29,3 @@ def get_evaluation_service_config( .get(ContainerSpec.CONTAINER_SPEC, {}) .get(container, {}) ) - - -# TODO: move this to global config.json in object storage -def get_finetuning_config_defaults(): - """Generate and return the fine-tuning default configuration dictionary.""" - return { - "shape": { - "VM.GPU.A10.1": {"batch_size": 1, "replica": "1-10"}, - "VM.GPU.A10.2": {"batch_size": 1, "replica": "1-10"}, - "BM.GPU.A10.4": {"batch_size": 1, "replica": 1}, - "BM.GPU4.8": {"batch_size": 4, "replica": 1}, - "BM.GPU.L40S-NC.4": {"batch_size": 4, "replica": 1}, - "BM.GPU.A100-v2.8": {"batch_size": 6, "replica": 1}, - "BM.GPU.H100.8": {"batch_size": 6, "replica": 1}, - } - } diff --git a/ads/aqua/config/deployment_config_defaults.json b/ads/aqua/config/deployment_config_defaults.json deleted file mode 100644 index 9caa8ef11..000000000 --- a/ads/aqua/config/deployment_config_defaults.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "configuration": { - "VM.Standard.A1.Flex": { - "parameters": {}, - "shape_info": { - "configs": [ - { - "memory_in_gbs": 128, - "ocpu": 20 - }, - { - "memory_in_gbs": 256, - "ocpu": 40 - }, - { - "memory_in_gbs": 384, - "ocpu": 60 - }, - { - "memory_in_gbs": 512, - "ocpu": 80 - } - ], - "type": "CPU" - } - } - }, - "shape": [ - "VM.GPU.A10.1", - "VM.GPU.A10.2", - "BM.GPU.A10.4", - "BM.GPU4.8", - "BM.GPU.L40S-NC.4", - "BM.GPU.A100-v2.8", - "BM.GPU.H100.8", - "VM.Standard.A1.Flex" - ] -} diff --git a/ads/aqua/config/resource_limit_names.json b/ads/aqua/config/resource_limit_names.json deleted file mode 100644 index 3aabcfaee..000000000 --- a/ads/aqua/config/resource_limit_names.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "BM.GPU.A10.4": "ds-gpu-a10-count", - "BM.GPU.A100-v2.8": "ds-gpu-a100-v2-count", - "BM.GPU.H100.8": "ds-gpu-h100-count", - "BM.GPU.L40S-NC.4": "ds-gpu-l40s-nc-count", - "BM.GPU4.8": "ds-gpu4-count", - "VM.GPU.A10.1": "ds-gpu-a10-count", - "VM.GPU.A10.2": "ds-gpu-a10-count" -} diff --git a/ads/aqua/extension/finetune_handler.py b/ads/aqua/extension/finetune_handler.py index ba82b2dc1..c8ebc5916 100644 --- a/ads/aqua/extension/finetune_handler.py +++ b/ads/aqua/extension/finetune_handler.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Copyright (c) 2024 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ @@ -9,8 +8,8 @@ from tornado.web import HTTPError from ads.aqua.common.decorator import handle_exceptions -from ads.aqua.extension.errors import Errors from ads.aqua.extension.base_handler import AquaAPIhandler +from ads.aqua.extension.errors import Errors from ads.aqua.extension.utils import validate_function_parameters from ads.aqua.finetuning import AquaFineTuningApp from ads.aqua.finetuning.entities import CreateFineTuningDetails diff --git a/ads/aqua/extension/ui_handler.py b/ads/aqua/extension/ui_handler.py index 89d7ea7c3..732151bf0 100644 --- a/ads/aqua/extension/ui_handler.py +++ b/ads/aqua/extension/ui_handler.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Copyright (c) 2024 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ @@ -11,8 +10,8 @@ from ads.aqua.common.decorator import handle_exceptions from ads.aqua.common.enums import Tags from ads.aqua.constants import PRIVATE_ENDPOINT_TYPE -from ads.aqua.extension.errors import Errors from ads.aqua.extension.base_handler import AquaAPIhandler +from ads.aqua.extension.errors import Errors from ads.aqua.extension.utils import validate_function_parameters from ads.aqua.model.entities import ImportModelDetails from ads.aqua.ui import AquaUIApp @@ -181,7 +180,9 @@ def list_subnets(self, **kwargs): def list_private_endpoints(self, **kwargs): """Lists the private endpoints in the specified compartment.""" compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID) - resource_type = self.get_argument("resource_type", default=PRIVATE_ENDPOINT_TYPE) + resource_type = self.get_argument( + "resource_type", default=PRIVATE_ENDPOINT_TYPE + ) return self.finish( AquaUIApp().list_private_endpoints( compartment_id=compartment_id, resource_type=resource_type, **kwargs @@ -193,10 +194,14 @@ def get_shape_availability(self, **kwargs): with the given limit.""" compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID) instance_shape = self.get_argument("instance_shape") + limit_name = self.get_argument("limit_name") return self.finish( AquaUIApp().get_shape_availability( - compartment_id=compartment_id, instance_shape=instance_shape, **kwargs + compartment_id=compartment_id, + instance_shape=instance_shape, + limit_name=limit_name, + **kwargs, ) ) diff --git a/ads/aqua/finetuning/entities.py b/ads/aqua/finetuning/entities.py index f17788f03..d096e190f 100644 --- a/ads/aqua/finetuning/entities.py +++ b/ads/aqua/finetuning/entities.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Copyright (c) 2024 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ from dataclasses import dataclass, field @@ -14,9 +13,9 @@ class AquaFineTuningParams(DataClassSerializable): epochs: int learning_rate: Optional[float] = None sample_packing: Optional[bool] = "auto" - batch_size: Optional[ - int - ] = None # make it batch_size for user, but internally this is micro_batch_size + batch_size: Optional[int] = ( + None # make it batch_size for user, but internally this is micro_batch_size + ) sequence_len: Optional[int] = None pad_to_sequence_len: Optional[bool] = None lora_r: Optional[int] = None @@ -24,6 +23,8 @@ class AquaFineTuningParams(DataClassSerializable): lora_dropout: Optional[float] = None lora_target_linear: Optional[bool] = None lora_target_modules: Optional[List] = None + early_stopping_patience: Optional[int] = None + early_stopping_threshold: Optional[float] = None @dataclass(repr=False) diff --git a/ads/aqua/finetuning/finetuning.py b/ads/aqua/finetuning/finetuning.py index 89eb07f6d..2fc52e441 100644 --- a/ads/aqua/finetuning/finetuning.py +++ b/ads/aqua/finetuning/finetuning.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # Copyright (c) 2024 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ import json import os -from dataclasses import asdict, fields, MISSING +from dataclasses import MISSING, asdict, fields from typing import Dict from oci.data_science.models import ( @@ -14,7 +13,7 @@ UpdateModelProvenanceDetails, ) -from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger +from ads.aqua import logger from ads.aqua.app import AquaApp from ads.aqua.common.enums import Resource, Tags from ads.aqua.common.errors import AquaFileExistsError, AquaValueError @@ -31,7 +30,6 @@ UNKNOWN, UNKNOWN_DICT, ) -from ads.aqua.config.config import get_finetuning_config_defaults from ads.aqua.data import AquaResourceIdentifier from ads.aqua.finetuning.constants import * from ads.aqua.finetuning.entities import * @@ -132,7 +130,7 @@ def create( or create_fine_tuning_details.validation_set_size >= 1 ): raise AquaValueError( - f"Fine tuning validation set size should be a float number in between [0, 1)." + "Fine tuning validation set size should be a float number in between [0, 1)." ) if create_fine_tuning_details.replica < DEFAULT_FT_REPLICA: @@ -394,7 +392,7 @@ def create( ) # track shapes that were used for fine-tune creation self.telemetry.record_event_async( - category=f"aqua/service/finetune/create/shape/", + category="aqua/service/finetune/create/shape/", action=f"{create_fine_tuning_details.shape_name}x{create_fine_tuning_details.replica}", **telemetry_kwargs, ) @@ -533,6 +531,12 @@ def _build_oci_launch_cmd( oci_launch_cmd += f"--num_{key} {value} " elif key == "lora_target_modules": oci_launch_cmd += f"--{key} {','.join(str(k) for k in value)} " + elif key == "early_stopping_patience": + if value != 0: + oci_launch_cmd += f"--{key} {value} " + elif key == "early_stopping_threshold": + if "early_stopping_patience" in oci_launch_cmd: + oci_launch_cmd += f"--{key} {value} " else: oci_launch_cmd += f"--{key} {value} " @@ -558,8 +562,9 @@ def get_finetuning_config(self, model_id: str) -> Dict: config = self.get_config(model_id, AQUA_MODEL_FINETUNING_CONFIG) if not config: - logger.info(f"Fetching default fine-tuning config for model: {model_id}") - config = get_finetuning_config_defaults() + logger.debug( + f"Fine-tuning config for custom model: {model_id} is not available." + ) return config @telemetry( diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 4a1879d83..b27d28c49 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -14,7 +14,7 @@ from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger from ads.aqua.app import AquaApp -from ads.aqua.common.enums import Tags +from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( LifecycleStatus, @@ -933,139 +933,186 @@ def _validate_model( # now as we know that at least one type of model files exist, validate the content of oss path. # for safetensors, we check if config.json files exist, and for gguf format we check if files with # gguf extension exist. - for model_format in model_formats: + if {ModelFormat.SAFETENSORS, ModelFormat.GGUF}.issubset(set(model_formats)): if ( - model_format == ModelFormat.SAFETENSORS - and len(safetensors_model_files) > 0 + import_model_details.inference_container.lower() == InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY ): - if import_model_details.download_from_hf: - # validates config.json exists for safetensors model from hugginface - if not hf_download_config_present: - raise AquaRuntimeError( - f"The model {model_name} does not contain {AQUA_MODEL_ARTIFACT_CONFIG} file as required " - f"by {ModelFormat.SAFETENSORS.value} format model." - f" Please check if the model name is correct in Hugging Face repository." - ) - else: - try: - model_config = load_config( - file_path=import_model_details.os_path, - config_file_name=AQUA_MODEL_ARTIFACT_CONFIG, - ) - except Exception as ex: - logger.error( - f"Exception occurred while loading config file from {import_model_details.os_path}" - f"Exception message: {ex}" - ) - raise AquaRuntimeError( - f"The model path {import_model_details.os_path} does not contain the file config.json. " - f"Please check if the path is correct or the model artifacts are available at this location." - ) from ex - else: - try: - 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 ( - model_config[ - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - ] - != metadata_model_type - ): - raise AquaRuntimeError( - f"The {AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE} attribute in {AQUA_MODEL_ARTIFACT_CONFIG}" - f" at {import_model_details.os_path} is invalid, expected {metadata_model_type} for " - f"the model {model_name}. Please check if the path is correct or " - f"the correct model artifacts are available at this location." - f"" - ) - else: - logger.debug( - f"Could not find {AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE} attribute in " - f"{AQUA_MODEL_ARTIFACT_CONFIG}. Proceeding with model registration." - ) - except Exception: - pass - if verified_model: - 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 - ): - validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME]}" - elif ( - model_config is not None - and AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE in model_config + 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 + ) + else: + self._validate_safetensor_format( + import_model_details=import_model_details, + verified_model=verified_model, + validation_result=validation_result, + hf_download_config_present=hf_download_config_present, + model_name=model_name + ) + elif ModelFormat.SAFETENSORS in model_formats: + self._validate_safetensor_format( + import_model_details=import_model_details, + verified_model=verified_model, + validation_result=validation_result, + hf_download_config_present=hf_download_config_present, + model_name=model_name + ) + elif ModelFormat.GGUF in model_formats: + 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 + ) + + return validation_result + + @staticmethod + def _validate_safetensor_format( + import_model_details: ImportModelDetails = None, + verified_model: DataScienceModel = None, + validation_result: ModelValidationResult = None, + hf_download_config_present: bool = None, + model_name: str = None + ): + if import_model_details.download_from_hf: + # validates config.json exists for safetensors model from hugginface + if not hf_download_config_present: + raise AquaRuntimeError( + f"The model {model_name} does not contain {AQUA_MODEL_ARTIFACT_CONFIG} file as required " + f"by {ModelFormat.SAFETENSORS.value} format model." + f" Please check if the model name is correct in Hugging Face repository." + ) + else: + try: + model_config = load_config( + file_path=import_model_details.os_path, + config_file_name=AQUA_MODEL_ARTIFACT_CONFIG, + ) + except Exception as ex: + logger.error( + f"Exception occurred while loading config file from {import_model_details.os_path}" + f"Exception message: {ex}" + ) + raise AquaRuntimeError( + f"The model path {import_model_details.os_path} does not contain the file config.json. " + f"Please check if the path is correct or the model artifacts are available at this location." + ) from ex + else: + try: + 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 ): - validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE]}" + if ( + model_config[ + AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE + ] + != metadata_model_type + ): + raise AquaRuntimeError( + f"The {AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE} attribute in {AQUA_MODEL_ARTIFACT_CONFIG}" + f" at {import_model_details.os_path} is invalid, expected {metadata_model_type} for " + f"the model {model_name}. Please check if the path is correct or " + f"the correct model artifacts are available at this location." + f"" + ) else: - validation_result.telemetry_model_name = ( - AQUA_MODEL_TYPE_CUSTOM + logger.debug( + f"Could not find {AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE} attribute in " + f"{AQUA_MODEL_ARTIFACT_CONFIG}. Proceeding with model registration." ) - elif model_format == ModelFormat.GGUF and len(gguf_model_files) > 0: - if import_model_details.finetuning_container and not safetensors_model_files: - raise AquaValueError( - "Fine-tuning is currently not supported with GGUF model format." - ) + except Exception: + pass if verified_model: - try: - model_file = verified_model.custom_metadata_list.get( - AQUA_MODEL_ARTIFACT_FILE - ).value - except ValueError as err: - raise AquaRuntimeError( - f"The model {verified_model.display_name} does not contain the custom metadata {AQUA_MODEL_ARTIFACT_FILE}. " - f"Please check if the model has the valid metadata." - ) from err - else: - model_file = import_model_details.model_file - - model_files = gguf_model_files - # todo: have a separate error validation class for different type of error messages. - if model_file: - if model_file not in model_files: - raise AquaRuntimeError( - f"The model path {import_model_details.os_path} or the Hugging Face " - f"model repository for {model_name} does not contain the file " - f"{model_file}. Please check if the path is correct or the model " - f"artifacts are available at this location." - ) - else: - validation_result.model_file = model_file - elif len(model_files) == 0: - raise AquaRuntimeError( - f"The model path {import_model_details.os_path} or the Hugging Face model " - f"repository for {model_name} does not contain any GGUF format files. " - f"Please check if the path is correct or the model artifacts are available " - f"at this location." - ) - elif len(model_files) > 1: - raise AquaRuntimeError( - f"The model path {import_model_details.os_path} or the Hugging Face model " - f"repository for {model_name} contains multiple GGUF format files. " - f"Please specify the file that needs to be deployed using the model_file " - f"parameter." + 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 + ): + validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME]}" + elif ( + model_config is not None + and AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE in model_config + ): + validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE]}" else: - validation_result.model_file = model_files[0] + validation_result.telemetry_model_name = ( + AQUA_MODEL_TYPE_CUSTOM + ) - if verified_model: - validation_result.telemetry_model_name = verified_model.display_name - elif import_model_details.download_from_hf: - validation_result.telemetry_model_name = model_name - else: - validation_result.telemetry_model_name = AQUA_MODEL_TYPE_CUSTOM + @staticmethod + def _validate_gguf_format( + import_model_details: ImportModelDetails = None, + verified_model: DataScienceModel = None, + gguf_model_files: List[str] = None, + validation_result: ModelValidationResult = None, + model_name: str = None, + ): + if import_model_details.finetuning_container: + raise AquaValueError( + "Fine-tuning is currently not supported with GGUF model format." + ) + if verified_model: + try: + model_file = verified_model.custom_metadata_list.get( + AQUA_MODEL_ARTIFACT_FILE + ).value + except ValueError as err: + raise AquaRuntimeError( + f"The model {verified_model.display_name} does not contain the custom metadata {AQUA_MODEL_ARTIFACT_FILE}. " + f"Please check if the model has the valid metadata." + ) from err + else: + model_file = import_model_details.model_file - return validation_result + model_files = gguf_model_files + # todo: have a separate error validation class for different type of error messages. + if model_file: + if model_file not in model_files: + raise AquaRuntimeError( + f"The model path {import_model_details.os_path} or the Hugging Face " + f"model repository for {model_name} does not contain the file " + f"{model_file}. Please check if the path is correct or the model " + f"artifacts are available at this location." + ) + else: + validation_result.model_file = model_file + elif len(model_files) == 0: + raise AquaRuntimeError( + f"The model path {import_model_details.os_path} or the Hugging Face model " + f"repository for {model_name} does not contain any GGUF format files. " + f"Please check if the path is correct or the model artifacts are available " + f"at this location." + ) + elif len(model_files) > 1: + raise AquaRuntimeError( + f"The model path {import_model_details.os_path} or the Hugging Face model " + f"repository for {model_name} contains multiple GGUF format files. " + f"Please specify the file that needs to be deployed using the model_file " + f"parameter." + ) + else: + validation_result.model_file = model_files[0] + + if verified_model: + validation_result.telemetry_model_name = verified_model.display_name + elif import_model_details.download_from_hf: + validation_result.telemetry_model_name = model_name + else: + validation_result.telemetry_model_name = AQUA_MODEL_TYPE_CUSTOM @staticmethod def _download_model_from_hf( diff --git a/ads/aqua/modeldeployment/deployment.py b/ads/aqua/modeldeployment/deployment.py index 8d3a07e8d..1f32a1ff9 100644 --- a/ads/aqua/modeldeployment/deployment.py +++ b/ads/aqua/modeldeployment/deployment.py @@ -23,7 +23,6 @@ get_params_list, get_resource_name, get_restricted_params_by_container, - load_config, ) from ads.aqua.constants import ( AQUA_MODEL_ARTIFACT_FILE, @@ -44,10 +43,8 @@ from ads.common.object_storage_details import ObjectStorageDetails from ads.common.utils import get_log_links from ads.config import ( - AQUA_CONFIG_FOLDER, AQUA_DEPLOYMENT_CONTAINER_METADATA_NAME, AQUA_MODEL_DEPLOYMENT_CONFIG, - AQUA_MODEL_DEPLOYMENT_CONFIG_DEFAULTS, COMPARTMENT_OCID, ) from ads.model.datascience_model import DataScienceModel @@ -581,10 +578,8 @@ def get_deployment_config(self, model_id: str) -> Dict: """ config = self.get_config(model_id, AQUA_MODEL_DEPLOYMENT_CONFIG) if not config: - logger.info(f"Fetching default deployment config for model: {model_id}") - config = load_config( - AQUA_CONFIG_FOLDER, - config_file_name=AQUA_MODEL_DEPLOYMENT_CONFIG_DEFAULTS, + logger.debug( + f"Deployment config for custom model: {model_id} is not available." ) return config diff --git a/ads/aqua/ui.py b/ads/aqua/ui.py index 5f2ae6f7e..39fa63f09 100644 --- a/ads/aqua/ui.py +++ b/ads/aqua/ui.py @@ -17,15 +17,13 @@ from ads.aqua.common.entities import ContainerSpec from ads.aqua.common.enums import Tags from ads.aqua.common.errors import AquaResourceAccessError, AquaValueError -from ads.aqua.common.utils import get_container_config, load_config, sanitize_response +from ads.aqua.common.utils import get_container_config, sanitize_response from ads.aqua.constants import PRIVATE_ENDPOINT_TYPE from ads.common import oci_client as oc from ads.common.auth import default_signer from ads.common.object_storage_details import ObjectStorageDetails from ads.common.serializer import DataClassSerializable from ads.config import ( - AQUA_CONFIG_FOLDER, - AQUA_RESOURCE_LIMIT_NAMES_CONFIG, COMPARTMENT_OCID, DATA_SCIENCE_SERVICE_NAME, TENANCY_OCID, @@ -568,9 +566,7 @@ def list_private_endpoints(self, **kwargs) -> list: """ compartment_id = kwargs.pop("compartment_id", COMPARTMENT_OCID) resource_type = kwargs.pop("resource_type", PRIVATE_ENDPOINT_TYPE) - logger.info( - f"Loading private endpoints from compartment: {compartment_id}" - ) + logger.info(f"Loading private endpoints from compartment: {compartment_id}") res = self.ds_client.list_data_science_private_endpoints( compartment_id=compartment_id, data_science_resource_type=resource_type @@ -601,25 +597,13 @@ def get_shape_availability(self, **kwargs): """ compartment_id = kwargs.pop("compartment_id", COMPARTMENT_OCID) instance_shape = kwargs.pop("instance_shape", None) + limit_name = kwargs.pop("limit_name", None) if not instance_shape: raise AquaValueError("instance_shape argument is required.") limits_client = oc.OCIClientFactory(**default_signer()).limits - artifact_path = AQUA_CONFIG_FOLDER - config = load_config( - artifact_path, - config_file_name=AQUA_RESOURCE_LIMIT_NAMES_CONFIG, - ) - - if instance_shape not in config: - logger.error( - f"{instance_shape} does not have mapping details in {AQUA_RESOURCE_LIMIT_NAMES_CONFIG}" - ) - return {} - - limit_name = config[instance_shape] try: res = limits_client.get_resource_availability( DATA_SCIENCE_SERVICE_NAME, limit_name, compartment_id, **kwargs diff --git a/ads/config.py b/ads/config.py index cb4d7df10..ec6c91396 100644 --- a/ads/config.py +++ b/ads/config.py @@ -57,12 +57,6 @@ AQUA_CONTAINER_INDEX_CONFIG = os.environ.get( "AQUA_CONTAINER_INDEX_CONFIG", "container_index.json" ) -AQUA_MODEL_DEPLOYMENT_CONFIG_DEFAULTS = os.environ.get( - "AQUA_MODEL_DEPLOYMENT_CONFIG_DEFAULTS", "deployment_config_defaults.json" -) -AQUA_RESOURCE_LIMIT_NAMES_CONFIG = os.environ.get( - "AQUA_RESOURCE_LIMIT_NAMES_CONFIG", "resource_limit_names.json" -) AQUA_DEPLOYMENT_CONTAINER_METADATA_NAME = "deployment-container" AQUA_FINETUNING_CONTAINER_METADATA_NAME = "finetune-container" AQUA_EVALUATION_CONTAINER_METADATA_NAME = "evaluation-container" diff --git a/tests/unitary/with_extras/aqua/test_deployment.py b/tests/unitary/with_extras/aqua/test_deployment.py index 6a4840ea9..f50a73721 100644 --- a/tests/unitary/with_extras/aqua/test_deployment.py +++ b/tests/unitary/with_extras/aqua/test_deployment.py @@ -336,8 +336,7 @@ def test_get_deployment_missing_tags(self): self.app.get(model_deployment_id=TestDataset.MODEL_DEPLOYMENT_ID) - @patch("ads.aqua.modeldeployment.deployment.load_config") - def test_get_deployment_config(self, mock_load_config): + def test_get_deployment_config(self): """Test for fetching config details for a given deployment.""" config_json = os.path.join( @@ -351,9 +350,8 @@ def test_get_deployment_config(self, mock_load_config): assert result == config self.app.get_config = MagicMock(return_value=None) - mock_load_config.return_value = config result = self.app.get_deployment_config(TestDataset.MODEL_ID) - assert result == config + assert result == None @patch("ads.aqua.modeldeployment.deployment.get_container_config") @patch("ads.aqua.model.AquaModelApp.create") diff --git a/tests/unitary/with_extras/aqua/test_finetuning.py b/tests/unitary/with_extras/aqua/test_finetuning.py index 93c32f114..6fccb5aae 100644 --- a/tests/unitary/with_extras/aqua/test_finetuning.py +++ b/tests/unitary/with_extras/aqua/test_finetuning.py @@ -26,7 +26,6 @@ from ads.model.datascience_model import DataScienceModel from ads.model.model_metadata import ModelCustomMetadata from ads.aqua.common.errors import AquaValueError -from ads.aqua.config.config import get_finetuning_config_defaults class FineTuningTestCase(TestCase): @@ -246,10 +245,6 @@ def test_get_finetuning_config(self): result = self.app.get_finetuning_config(model_id="test-model-id") assert result == config - self.app.get_config = MagicMock(return_value=None) - result = self.app.get_finetuning_config(model_id="test-model-id") - assert result == get_finetuning_config_defaults() - def test_get_finetuning_default_params(self): """Test for fetching finetuning config params for a given model.""" diff --git a/tests/unitary/with_extras/aqua/test_ui.py b/tests/unitary/with_extras/aqua/test_ui.py index 70c8682b3..82bec51e6 100644 --- a/tests/unitary/with_extras/aqua/test_ui.py +++ b/tests/unitary/with_extras/aqua/test_ui.py @@ -20,7 +20,6 @@ from ads.aqua.common.errors import AquaValueError from ads.aqua.common.utils import load_config from ads.aqua.ui import AquaUIApp -from ads.config import AQUA_CONFIG_FOLDER, AQUA_RESOURCE_LIMIT_NAMES_CONFIG class TestDataset: @@ -28,7 +27,8 @@ class TestDataset: USER_COMPARTMENT_ID = "ocid1.compartment.oc1.." TENANCY_OCID = "ocid1.tenancy.oc1.." VCN_ID = "ocid1.vcn.oc1.iad." - DEPLOYMENT_SHAPE_NAMES = ["VM.GPU.A10.1", "BM.GPU4.8", "VM.Standard.2.16"] + DEPLOYMENT_SHAPE_NAMES = ["VM.GPU.A10.1", "BM.GPU4.8", "VM.GPU.A10.2"] + LIMIT_NAMES = ["ds-gpu-a10-count", "ds-gpu4-count", "ds-gpu-a10-count"] class TestAquaUI(unittest.TestCase): @@ -333,7 +333,7 @@ def test_list_vcn(self, mock_list_vcns): mock_list_vcns.return_value.data = [oci.core.models.Vcn(**vcn) for vcn in vcns] results = self.app.list_vcn() - mock_list_vcns.called_once() + mock_list_vcns.assert_called_once() expected_attributes = { "cidrBlock", "cidrBlocks", @@ -370,7 +370,7 @@ def test_list_subnets(self, mock_list_subnets): ] results = self.app.list_subnets(vcn_id=TestDataset.VCN_ID) - mock_list_subnets.called_once() + mock_list_subnets.assert_called_once() expected_attributes = { "cidrBlock", "compartmentId", @@ -432,7 +432,7 @@ def test_list_private_endpoints(self): "subnetId", "fqdn", "timeCreated", - "timeUpdated" + "timeUpdated", } for result in results: self.assertTrue( @@ -451,20 +451,14 @@ def test_get_shape_availability(self, mock_get_resource_availability): with open(resource_availability_json, "r") as _file: resource_availability = json.load(_file) - artifact_path = AQUA_CONFIG_FOLDER - config = load_config( - artifact_path, - config_file_name=AQUA_RESOURCE_LIMIT_NAMES_CONFIG, - ) - mock_get_resource_availability.return_value.data = ( oci.limits.models.ResourceAvailability(**resource_availability) ) result = self.app.get_shape_availability( - instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[0] + instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[0], + limit_name=TestDataset.LIMIT_NAMES[0], ) - mock_get_resource_availability.called_once() expected_attributes = {"available_count"} self.assertTrue( expected_attributes.issuperset(set(result)), "Attributes mismatch" @@ -474,10 +468,11 @@ def test_get_shape_availability(self, mock_get_resource_availability): with pytest.raises( AquaValueError, match=f"Inadequate resource is available to create the {TestDataset.DEPLOYMENT_SHAPE_NAMES[1]} resource. The number of available " - f"resource associated with the limit name {config[TestDataset.DEPLOYMENT_SHAPE_NAMES[1]]} is {resource_availability['available']}.", + f"resource associated with the limit name {TestDataset.LIMIT_NAMES[1]} is {resource_availability['available']}.", ): self.app.get_shape_availability( - instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[1] + instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[1], + limit_name=TestDataset.LIMIT_NAMES[1], ) with pytest.raises( @@ -487,9 +482,10 @@ def test_get_shape_availability(self, mock_get_resource_availability): self.app.get_shape_availability(instance_shape="") result = self.app.get_shape_availability( - instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[2] + instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAMES[2], + limit_name=TestDataset.LIMIT_NAMES[2], ) - assert result == {} + assert result == {"available_count": 2} @parameterized.expand([True, False]) @patch("ads.common.object_storage_details.ObjectStorageDetails.from_path") diff --git a/tests/unitary/with_extras/aqua/test_ui_handler.py b/tests/unitary/with_extras/aqua/test_ui_handler.py index c34e52ad4..17f23581d 100644 --- a/tests/unitary/with_extras/aqua/test_ui_handler.py +++ b/tests/unitary/with_extras/aqua/test_ui_handler.py @@ -23,6 +23,7 @@ class TestDataset: USER_PROJECT_ID = "ocid1.datascienceproject.oc1.iad." PRIVATE_ENDPOINT_RESOURCE_TYPE = "MODEL_DEPLOYMENT" DEPLOYMENT_SHAPE_NAME = "VM.GPU.A10.1" + LIMIT_NAME = "ds-gpu-a10-count" class TestAquaUIHandler(unittest.TestCase): @@ -157,14 +158,17 @@ def test_list_private_endpoints(self, mock_list_private_endpoints): self.ui_handler.get() mock_list_private_endpoints.assert_called_with( compartment_id=TestDataset.USER_COMPARTMENT_ID, - resource_type=TestDataset.PRIVATE_ENDPOINT_RESOURCE_TYPE + resource_type=TestDataset.PRIVATE_ENDPOINT_RESOURCE_TYPE, ) @patch("ads.aqua.ui.AquaUIApp.get_shape_availability") def test_get_shape_availability(self, mock_get_shape_availability): """Test get shape availability.""" self.ui_handler.request.path = "aqua/shapes/limit" - args = {"instance_shape": TestDataset.DEPLOYMENT_SHAPE_NAME} + args = { + "instance_shape": TestDataset.DEPLOYMENT_SHAPE_NAME, + "limit_name": TestDataset.LIMIT_NAME, + } self.ui_handler.get_argument = MagicMock( side_effect=lambda arg, default=None: args.get(arg, default) ) @@ -172,6 +176,7 @@ def test_get_shape_availability(self, mock_get_shape_availability): mock_get_shape_availability.assert_called_with( compartment_id=TestDataset.USER_COMPARTMENT_ID, instance_shape=TestDataset.DEPLOYMENT_SHAPE_NAME, + limit_name=TestDataset.LIMIT_NAME, ) @patch("ads.aqua.ui.AquaUIApp.is_bucket_versioned")