diff options
| author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
|---|---|---|
| committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
| commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
| tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning | |
| parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
| download | gn-ai-master.tar.gz | |
Diffstat (limited to '.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning')
6 files changed, 1056 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/__init__.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/__init__.py new file mode 100644 index 00000000..fdf8caba --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/__init__.py @@ -0,0 +1,5 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +__path__ = __import__("pkgutil").extend_path(__path__, __name__) diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_finetuning_job.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_finetuning_job.py new file mode 100644 index 00000000..e659c634 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_finetuning_job.py @@ -0,0 +1,242 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +# pylint: disable=protected-access + +from typing import Any, Dict + +from azure.ai.ml._restclient.v2024_01_01_preview.models import ( + ModelProvider as RestModelProvider, + AzureOpenAiFineTuning as RestAzureOpenAIFineTuning, + FineTuningJob as RestFineTuningJob, + JobBase as RestJobBase, +) +from azure.ai.ml.constants._common import BASE_PATH_CONTEXT_KEY +from azure.ai.ml.entities._job._input_output_helpers import from_rest_data_outputs, to_rest_data_outputs + +from azure.ai.ml.entities._job.finetuning.finetuning_vertical import FineTuningVertical +from azure.ai.ml.entities._job.finetuning.azure_openai_hyperparameters import AzureOpenAIHyperparameters +from azure.ai.ml.entities._util import load_from_dict +from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, ValidationException +from azure.ai.ml._utils._experimental import experimental + + +@experimental +class AzureOpenAIFineTuningJob(FineTuningVertical): + def __init__( + self, + **kwargs: Any, + ) -> None: + # Extract any task specific settings + model = kwargs.pop("model", None) + task = kwargs.pop("task", None) + # Convert task to lowercase first letter, this is when we create + # object from the schema, using dict object from the REST api response. + # TextCompletion => textCompletion + if task: + task = task[0].lower() + task[1:] + training_data = kwargs.pop("training_data", None) + validation_data = kwargs.pop("validation_data", None) + hyperparameters = kwargs.pop("hyperparameters", None) + if hyperparameters and not isinstance(hyperparameters, AzureOpenAIHyperparameters): + raise ValidationException( + category=ErrorCategory.USER_ERROR, + target=ErrorTarget.JOB, + message="Hyperparameters if provided should of type AzureOpenAIHyperparameters", + no_personal_data_message="Hyperparameters if provided should of type AzureOpenAIHyperparameters", + ) + + self._hyperparameters = hyperparameters + + super().__init__( + task=task, + model=model, + model_provider=RestModelProvider.AZURE_OPEN_AI, + training_data=training_data, + validation_data=validation_data, + **kwargs, + ) + + @property + def hyperparameters(self) -> AzureOpenAIHyperparameters: + """Get hyperparameters. + + :return: Hyperparameters for finetuning the model. + :rtype: AzureOpenAIHyperparameters + """ + return self._hyperparameters + + @hyperparameters.setter + def hyperparameters(self, hyperparameters: AzureOpenAIHyperparameters) -> None: + """Set hyperparameters. + + :param hyperparameters: Hyperparameters for finetuning the model. + :type hyperparameters: AzureOpenAiHyperParameters + """ + self._hyperparameters = hyperparameters + + def _to_rest_object(self) -> "RestFineTuningJob": + """Convert CustomFineTuningVertical object to a RestFineTuningJob object. + + :return: REST object representation of this object. + :rtype: JobBase + """ + aoai_finetuning_vertical = RestAzureOpenAIFineTuning( + task_type=self._task, + model=self._model, + model_provider=self._model_provider, + training_data=self._training_data, + validation_data=self._validation_data, + hyper_parameters=self.hyperparameters._to_rest_object() if self.hyperparameters else None, + ) + + self._resolve_inputs(aoai_finetuning_vertical) + + finetuning_job = RestFineTuningJob( + display_name=self.display_name, + description=self.description, + experiment_name=self.experiment_name, + tags=self.tags, + properties=self.properties, + fine_tuning_details=aoai_finetuning_vertical, + outputs=to_rest_data_outputs(self.outputs), + ) + + result = RestJobBase(properties=finetuning_job) + result.name = self.name + + return result + + def _to_dict(self) -> Dict: + """Convert the object to a dictionary. + + :return: dictionary representation of the object. + :rtype: typing.Dict + """ + from azure.ai.ml._schema._finetuning.azure_openai_finetuning import AzureOpenAIFineTuningSchema + + schema_dict: dict = {} + # TODO: Combeback to this later for FineTuningJob in Pipelines + # if inside_pipeline: + # schema_dict = AutoMLClassificationNodeSchema(context={BASE_PATH_CONTEXT_KEY: "./"}).dump(self) + # else: + schema_dict = AzureOpenAIFineTuningSchema(context={BASE_PATH_CONTEXT_KEY: "./"}).dump(self) + + return schema_dict + + def __eq__(self, other: object) -> bool: + """Returns True if both instances have the same values. + + This method check instances equality and returns True if both of + the instances have the same attributes with the same values. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + if not isinstance(other, AzureOpenAIFineTuningJob): + return NotImplemented + + return super().__eq__(other) and self.hyperparameters == other.hyperparameters + + def __ne__(self, other: object) -> bool: + """Check inequality between two AzureOpenAIFineTuningJob objects. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + return not self.__eq__(other) + + @classmethod + def _from_rest_object(cls, obj: RestJobBase) -> "AzureOpenAIFineTuningJob": + """Convert a REST object to AzureOpenAIFineTuningJob object. + + :param obj: AzureOpenAIFineTuningJob in Rest format. + :type obj: JobBase + :return: AzureOpenAIFineTuningJob objects. + :rtype: AzureOpenAIFineTuningJob + """ + + properties: RestFineTuningJob = obj.properties + finetuning_details: RestAzureOpenAIFineTuning = properties.fine_tuning_details + + job_args_dict = { + "id": obj.id, + "name": obj.name, + "description": properties.description, + "tags": properties.tags, + "properties": properties.properties, + "experiment_name": properties.experiment_name, + "status": properties.status, + "creation_context": obj.system_data, + "display_name": properties.display_name, + "outputs": from_rest_data_outputs(properties.outputs), + } + + aoai_finetuning_job = cls( + task=finetuning_details.task_type, + model=finetuning_details.model, + training_data=finetuning_details.training_data, + validation_data=finetuning_details.validation_data, + hyperparameters=AzureOpenAIHyperparameters._from_rest_object(finetuning_details.hyper_parameters), + **job_args_dict, + ) + + aoai_finetuning_job._restore_inputs() + + return aoai_finetuning_job + + @classmethod + def _load_from_dict( + cls, + data: Dict, + context: Dict, + additional_message: str, + **kwargs: Any, + ) -> "AzureOpenAIFineTuningJob": + """Load from a dictionary. + + :param data: dictionary representation of the object. + :type data: typing.Dict + :param context: dictionary containing the context. + :type context: typing.Dict + :param additional_message: additional message to be added to the error message. + :type additional_message: str + :return: AzureOpenAIFineTuningJob object. + :rtype: AzureOpenAIFineTuningJob + """ + from azure.ai.ml._schema._finetuning.azure_openai_finetuning import AzureOpenAIFineTuningSchema + + # TODO: Combeback to this later - Pipeline part. + # from azure.ai.ml._schema.pipeline.automl_node import AutoMLClassificationNodeSchema + + # if kwargs.pop("inside_pipeline", False): + # loaded_data = load_from_dict( + # AutoMLClassificationNodeSchema, + # data, + # context, + # additional_message, + # **kwargs, + # ) + # else: + loaded_data = load_from_dict(AzureOpenAIFineTuningSchema, data, context, additional_message, **kwargs) + + job_instance = cls._create_instance_from_schema_dict(loaded_data) + return job_instance + + @classmethod + def _create_instance_from_schema_dict(cls, loaded_data: Dict) -> "AzureOpenAIFineTuningJob": + """Create an instance from a schema dictionary. + + :param loaded_data: dictionary containing the data. + :type loaded_data: typing.Dict + :return: AzureOpenAIFineTuningJob object. + :rtype: AzureOpenAIFineTuningJob + """ + + job = AzureOpenAIFineTuningJob(**loaded_data) + return job diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_hyperparameters.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_hyperparameters.py new file mode 100644 index 00000000..2b420a46 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/azure_openai_hyperparameters.py @@ -0,0 +1,125 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +from typing import Optional +from azure.ai.ml.entities._mixins import RestTranslatableMixin +from azure.ai.ml._restclient.v2024_01_01_preview.models import ( + AzureOpenAiHyperParameters as RestAzureOpenAiHyperParameters, +) +from azure.ai.ml._utils._experimental import experimental + + +@experimental +class AzureOpenAIHyperparameters(RestTranslatableMixin): + """Hyperparameters for Azure OpenAI model finetuning.""" + + def __init__( + self, + *, + batch_size: Optional[int] = None, + learning_rate_multiplier: Optional[float] = None, + n_epochs: Optional[int] = None, + ): + """Initialize AzureOpenAIHyperparameters. + + param batch_size: Number of examples in each batch. + A larger batch size means that model parameters are updated less + frequently, but with lower variance. Defaults to None. + type batch_size: int + param learning_rate_multiplier: Scaling factor for the learning rate. + A smaller learning rate may be useful to avoid overfitting. + type learning_rate_multiplier: float + param n_epochs: The number of epochs to train the model for. + An epoch refers to one full cycle through the training dataset. + type n_epochs: int + """ + self._batch_size = batch_size + self._learning_rate_multiplier = learning_rate_multiplier + self._n_epochs = n_epochs + # Not exposed in the public API, so need to check how to handle this + # self._additional_properties = kwargs + + @property + def batch_size(self) -> Optional[int]: + """Get the batch size for training.""" + return self._batch_size + + @batch_size.setter + def batch_size(self, value: Optional[int]) -> None: + """Set the batch size for training. + :param value: The batch size for training. + :type value: int + """ + self._batch_size = value + + @property + def learning_rate_multiplier(self) -> Optional[float]: + """Get the learning rate multiplier. + :return: The learning rate multiplier. + :rtype: float + """ + return self._learning_rate_multiplier + + @learning_rate_multiplier.setter + def learning_rate_multiplier(self, value: Optional[float]) -> None: + """Set the learning rate multiplier. + :param value: The learning rate multiplier. + :type value: float + """ + self._learning_rate_multiplier = value + + @property + def n_epochs(self) -> Optional[int]: + """Get the number of epochs. + :return: The number of epochs. + :rtype: int + """ + return self._n_epochs + + @n_epochs.setter + def n_epochs(self, value: Optional[int]) -> None: + """Set the number of epochs. + :param value: The number of epochs. + :type value: int + """ + self._n_epochs = value + + # Not exposed in the public API, so need to check how to handle this + # @property + # def additional_properties(self) -> dict: + # """Get additional properties.""" + # return self._additional_properties + + # @additional_properties.setter + # def additional_properties(self, value: dict) -> None: + # """Set additional properties.""" + # self._additional_properties = value + + def _to_rest_object(self) -> RestAzureOpenAiHyperParameters: + return RestAzureOpenAiHyperParameters( + batch_size=self._batch_size, + learning_rate_multiplier=self._learning_rate_multiplier, + n_epochs=self._n_epochs, + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AzureOpenAIHyperparameters): + return NotImplemented + return ( + self._batch_size == other._batch_size + and self._learning_rate_multiplier == other._learning_rate_multiplier + and self._n_epochs == other._n_epochs + ) + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + @classmethod + def _from_rest_object(cls, obj: RestAzureOpenAiHyperParameters) -> "AzureOpenAIHyperparameters": + aoai_hyperparameters = cls( + batch_size=obj.batch_size, + learning_rate_multiplier=obj.learning_rate_multiplier, + n_epochs=obj.n_epochs, + ) + return aoai_hyperparameters diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/custom_model_finetuning_job.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/custom_model_finetuning_job.py new file mode 100644 index 00000000..e6ddd86d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/custom_model_finetuning_job.py @@ -0,0 +1,258 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +# pylint: disable=protected-access + +from typing import Any, Dict + +from azure.ai.ml._restclient.v2024_10_01_preview.models import ( + ModelProvider as RestModelProvider, + CustomModelFineTuning as RestCustomModelFineTuningVertical, + FineTuningJob as RestFineTuningJob, + JobBase as RestJobBase, +) +from azure.ai.ml.entities._job._input_output_helpers import ( + from_rest_data_outputs, + to_rest_data_outputs, +) +from azure.ai.ml.entities._job.job_resources import JobResources +from azure.ai.ml.entities._job.queue_settings import QueueSettings +from azure.ai.ml.entities._inputs_outputs import Input +from azure.ai.ml.constants._common import BASE_PATH_CONTEXT_KEY +from azure.ai.ml.entities._job.finetuning.finetuning_vertical import FineTuningVertical +from azure.ai.ml.entities._util import load_from_dict +from azure.ai.ml._utils._experimental import experimental + + +@experimental +class CustomModelFineTuningJob(FineTuningVertical): + def __init__( + self, + **kwargs: Any, + ) -> None: + # Extract any task specific settings + model = kwargs.pop("model", None) + task = kwargs.pop("task", None) + # Convert task to lowercase first letter, this is when we create + # object from the schema, using dict object from the REST api response. + # TextCompletion => textCompletion + if task: + task = task[0].lower() + task[1:] + training_data = kwargs.pop("training_data", None) + validation_data = kwargs.pop("validation_data", None) + self._hyperparameters = kwargs.pop("hyperparameters", None) + super().__init__( + task=task, + model=model, + model_provider=RestModelProvider.CUSTOM, + training_data=training_data, + validation_data=validation_data, + **kwargs, + ) + + @property + def hyperparameters(self) -> Dict[str, str]: + """Get hyperparameters. + + :return: + :rtype: hyperparameters: Dict[str,str] + """ + return self._hyperparameters + + @hyperparameters.setter + def hyperparameters(self, hyperparameters: Dict[str, str]) -> None: + """Set hyperparameters. + + :param hyperparameters: Hyperparameters for finetuning the model + :type hyperparameters: Dict[str,str] + """ + self._hyperparameters = hyperparameters + + def _to_rest_object(self) -> "RestFineTuningJob": + """Convert CustomFineTuningVertical object to a RestFineTuningJob object. + + :return: REST object representation of this object. + :rtype: JobBase + """ + custom_finetuning_vertical = RestCustomModelFineTuningVertical( + task_type=self._task, + model=self._model, + model_provider=self._model_provider, + training_data=self._training_data, + validation_data=self._validation_data, + hyper_parameters=self._hyperparameters, + ) + self._resolve_inputs(custom_finetuning_vertical) + + finetuning_job = RestFineTuningJob( + display_name=self.display_name, + description=self.description, + experiment_name=self.experiment_name, + services=self.services, + tags=self.tags, + properties=self.properties, + compute_id=self.compute, + fine_tuning_details=custom_finetuning_vertical, + outputs=to_rest_data_outputs(self.outputs), + ) + if self.resources: + finetuning_job.resources = self.resources._to_rest_object() + if self.queue_settings: + finetuning_job.queue_settings = self.queue_settings._to_rest_object() + + result = RestJobBase(properties=finetuning_job) + result.name = self.name + + return result + + def _to_dict(self) -> Dict: + """Convert the object to a dictionary. + + :return: dictionary representation of the object. + :rtype: typing.Dict + """ + from azure.ai.ml._schema._finetuning.custom_model_finetuning import ( + CustomModelFineTuningSchema, + ) + + schema_dict: dict = {} + # TODO: Combeback to this later for FineTuningJob in pipeline + # if inside_pipeline: + # schema_dict = AutoMLClassificationNodeSchema(context={BASE_PATH_CONTEXT_KEY: "./"}).dump(self) + # else: + schema_dict = CustomModelFineTuningSchema(context={BASE_PATH_CONTEXT_KEY: "./"}).dump(self) + + return schema_dict + + def __eq__(self, other: object) -> bool: + """Returns True if both instances have the same values. + + This method check instances equality and returns True if both of + the instances have the same attributes with the same values. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + if not isinstance(other, CustomModelFineTuningJob): + return NotImplemented + + return super().__eq__(other) and self.hyperparameters == other.hyperparameters + + def __ne__(self, other: object) -> bool: + """Check inequality between two CustomModelFineTuningJob objects. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + return not self.__eq__(other) + + @classmethod + def _from_rest_object(cls, obj: RestJobBase) -> "CustomModelFineTuningJob": + """Convert a REST object to CustomModelFineTuningJob object. + + :param obj: CustomModelFineTuningJob in Rest format. + :type obj: JobBase + :return: CustomModelFineTuningJob objects. + :rtype: CustomModelFineTuningJob + """ + + properties: RestFineTuningJob = obj.properties + finetuning_details: RestCustomModelFineTuningVertical = properties.fine_tuning_details + + job_args_dict = { + "id": obj.id, + "name": obj.name, + "description": properties.description, + "tags": properties.tags, + "properties": properties.properties, + "services": properties.services, + "experiment_name": properties.experiment_name, + "status": properties.status, + "creation_context": obj.system_data, + "display_name": properties.display_name, + "compute": properties.compute_id, + "outputs": from_rest_data_outputs(properties.outputs), + } + + if properties.resources: + job_args_dict["resources"] = JobResources._from_rest_object(properties.resources) + if properties.queue_settings: + job_args_dict["queue_settings"] = QueueSettings._from_rest_object(properties.queue_settings) + + custom_model_finetuning_job = cls( + task=finetuning_details.task_type, + model=finetuning_details.model, + training_data=finetuning_details.training_data, + validation_data=finetuning_details.validation_data, + hyperparameters=finetuning_details.hyper_parameters, + **job_args_dict, + ) + + custom_model_finetuning_job._restore_inputs() + + return custom_model_finetuning_job + + @classmethod + def _load_from_dict( + cls, + data: Dict, + context: Dict, + additional_message: str, + **kwargs: Any, + ) -> "CustomModelFineTuningJob": + """Load from a dictionary. + + :param data: dictionary representation of the object. + :type data: typing.Dict + :param context: dictionary containing the context. + :type context: typing.Dict + :param additional_message: additional message to be added to the error message. + :type additional_message: str + :return: CustomModelFineTuningJob object. + :rtype: CustomModelFineTuningJob + """ + from azure.ai.ml._schema._finetuning.custom_model_finetuning import ( + CustomModelFineTuningSchema, + ) + + # TODO: Combeback to this later - Pipeline part. + # from azure.ai.ml._schema.pipeline.automl_node import AutoMLClassificationNodeSchema + + # if kwargs.pop("inside_pipeline", False): + # loaded_data = load_from_dict( + # AutoMLClassificationNodeSchema, + # data, + # context, + # additional_message, + # **kwargs, + # ) + # else: + loaded_data = load_from_dict(CustomModelFineTuningSchema, data, context, additional_message, **kwargs) + + training_data = loaded_data.get("training_data", None) + if isinstance(training_data, str): + loaded_data["training_data"] = Input(type="uri_file", path=training_data) + + validation_data = loaded_data.get("validation_data", None) + if isinstance(validation_data, str): + loaded_data["validation_data"] = Input(type="uri_file", path=validation_data) + + job_instance = cls._create_instance_from_schema_dict(loaded_data) + return job_instance + + @classmethod + def _create_instance_from_schema_dict(cls, loaded_data: Dict) -> "CustomModelFineTuningJob": + """Create an instance from a schema dictionary. + + :param loaded_data: dictionary containing the data. + :type loaded_data: typing.Dict + :return: CustomModelFineTuningJob object. + :rtype: CustomModelFineTuningJob + """ + job = CustomModelFineTuningJob(**loaded_data) + return job diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_job.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_job.py new file mode 100644 index 00000000..ec8d9d5d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_job.py @@ -0,0 +1,224 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +# pylint: disable=protected-access + +from typing import Any, Dict, Optional + +from azure.ai.ml.entities._job.job import Job +from azure.ai.ml.entities._job.job_io_mixin import JobIOMixin +from azure.ai.ml._restclient.v2024_10_01_preview.models import ( + ModelProvider as RestModelProvider, + JobBase as RestJobBase, +) +from azure.ai.ml.constants import JobType +from azure.ai.ml.constants._common import TYPE +from azure.ai.ml._utils.utils import camel_to_snake +from azure.ai.ml.entities._job.job_resources import JobResources +from azure.ai.ml.entities._job.queue_settings import QueueSettings +from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, ValidationException +from azure.ai.ml.constants._job.finetuning import FineTuningConstants +from azure.ai.ml._utils._experimental import experimental + + +@experimental +class FineTuningJob(Job, JobIOMixin): + def __init__( + self, + **kwargs: Any, + ) -> None: + kwargs[TYPE] = JobType.FINE_TUNING + self.resources = kwargs.pop("resources", None) + self.queue_settings = kwargs.pop("queue_settings", None) + self.outputs = kwargs.pop("outputs", None) + super().__init__(**kwargs) + + @property + def resources(self) -> Optional[JobResources]: + """Job resources to use during job execution. + :return: Job Resources object. + :rtype: JobResources + """ + return self._resources if hasattr(self, "_resources") else None + + @resources.setter + def resources(self, value: JobResources) -> None: + """Set JobResources. + + :param value: JobResources object. + :type value: JobResources + :raises ValidationException: Expected a JobResources object. + """ + if isinstance(value, JobResources): + self._resources = value + elif value: + msg = "Expected an instance of JobResources." + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.FINETUNING, + error_category=ErrorCategory.USER_ERROR, + ) + + @property + def queue_settings(self) -> Optional[QueueSettings]: + """Queue settings for job execution. + :return: QueueSettings object. + :rtype: QueueSettings + """ + return self._queue_settings if hasattr(self, "_queue_settings") else None + + @queue_settings.setter + def queue_settings(self, value: QueueSettings) -> None: + """Set queue settings for job execution. + + :param value: QueueSettings object. + :type value: QueueSettings + :raises ValidationException: Expected a QueueSettings object. + """ + if isinstance(value, QueueSettings): + self._queue_settings = value + elif value: + msg = "Expected an instance of QueueSettings." + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.FINETUNING, + error_category=ErrorCategory.USER_ERROR, + ) + + def __eq__(self, other: object) -> bool: + """Returns True if both instances have the same values. + + This method check instances equality and returns True if both of + the instances have the same attributes with the same values. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + if not isinstance(other, FineTuningJob): + return NotImplemented + + queue_settings_match = (not self.queue_settings and not other.queue_settings) or ( + self.queue_settings is not None + and other.queue_settings is not None + and self.queue_settings.job_tier is not None + and other.queue_settings.job_tier is not None + and self.queue_settings.job_tier.lower() == other.queue_settings.job_tier.lower() + ) + + outputs_match = not self.outputs and not other.outputs + if self.outputs and other.outputs: + outputs_match = ( + self.outputs["registered_model"].name == other.outputs["registered_model"].name + and self.outputs["registered_model"].type == other.outputs["registered_model"].type + ) + + return ( + outputs_match + and self.resources == other.resources + and queue_settings_match + # add properties from base class + and self.name == other.name + and self.description == other.description + and self.tags == other.tags + and self.properties == other.properties + and self.compute == other.compute + and self.id == other.id + and self.experiment_name == other.experiment_name + and self.status == other.status + ) + + def __ne__(self, other: object) -> bool: + """Check inequality between two FineTuningJob objects. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + return not self.__eq__(other) + + @classmethod + def _get_model_provider_mapping(cls) -> Dict: + """Create a mapping of task type to job class. + + :return: An FineTuningVertical object containing the model provider type to job class mapping. + :rtype: FineTuningJob + """ + from .custom_model_finetuning_job import CustomModelFineTuningJob + from .azure_openai_finetuning_job import AzureOpenAIFineTuningJob + + return { + camel_to_snake(RestModelProvider.CUSTOM): CustomModelFineTuningJob, + camel_to_snake(RestModelProvider.AZURE_OPEN_AI): AzureOpenAIFineTuningJob, + } + + @classmethod + def _load_from_rest(cls, obj: RestJobBase) -> "FineTuningJob": + """Loads the rest object to a dict containing items to init the AutoMLJob objects. + + :param obj: Azure Resource Manager resource envelope. + :type obj: JobBase + :raises ValidationException: task type validation error + :return: A FineTuningJob + :rtype: FineTuningJob + """ + model_provider = ( + camel_to_snake(obj.properties.fine_tuning_details.model_provider) + if obj.properties.fine_tuning_details.model_provider + else None + ) + class_type = cls._get_model_provider_mapping().get(model_provider, None) + if class_type: + res: FineTuningJob = class_type._from_rest_object(obj) + return res + msg = f"Unsupported model provider type: {obj.properties.fine_tuning_details.model_provider}" + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.FINETUNING, + error_category=ErrorCategory.SYSTEM_ERROR, + ) + + @classmethod + def _load_from_dict( + cls, + data: Dict, + context: Dict, + additional_message: str, + **kwargs: Any, + ) -> "FineTuningJob": + """Loads the dictionary objects to an FineTuningJob object. + + :param data: A data dictionary. + :type data: typing.Dict + :param context: A context dictionary. + :type context: typing.Dict + :param additional_message: An additional message to be logged in the ValidationException. + :type additional_message: str + + :raises ValidationException: task type validation error + :return: An FineTuningJob + :rtype: FineTuningJob + """ + model_provider = data.get(FineTuningConstants.ModelProvider) + class_type = cls._get_model_provider_mapping().get(model_provider, None) + if class_type: + res: FineTuningJob = class_type._load_from_dict( + data, + context, + additional_message, + **kwargs, + ) + return res + msg = f"Unsupported model provider type: {model_provider}" + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.AUTOML, + error_category=ErrorCategory.USER_ERROR, + ) diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_vertical.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_vertical.py new file mode 100644 index 00000000..c9a5fe41 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/entities/_job/finetuning/finetuning_vertical.py @@ -0,0 +1,202 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + +from typing import Any, Optional, cast + +from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, ValidationException +from azure.ai.ml._restclient.v2024_10_01_preview.models import ( + ModelProvider as RestModelProvider, + FineTuningVertical as RestFineTuningVertical, + UriFileJobInput, + MLFlowModelJobInput, +) +from azure.ai.ml.constants._common import AssetTypes +from azure.ai.ml._utils.utils import camel_to_snake +from azure.ai.ml.entities._inputs_outputs import Input +from azure.ai.ml.entities._job.finetuning.finetuning_job import FineTuningJob + +from azure.ai.ml._utils._experimental import experimental + + +@experimental +class FineTuningVertical(FineTuningJob): + def __init__( + self, + *, + task: str, + model: Input, + model_provider: Optional[str], + training_data: Input, + validation_data: Optional[Input] = None, + **kwargs: Any, + ) -> None: + self._task = task + self._model = model + self._model_provider = model_provider + self._training_data = training_data + self._validation_data = validation_data + super().__init__(**kwargs) + + @property + def task(self) -> str: + """Get finetuning task. + + :return: The type of task to run. Possible values include: "ChatCompletion" + "TextCompletion", "TextClassification", "QuestionAnswering","TextSummarization", + "TokenClassification", "TextTranslation", "ImageClassification", "ImageInstanceSegmentation", + "ImageObjectDetection","VideoMultiObjectTracking". + + :rtype: str + """ + return self._task + + @task.setter + def task(self, task: str) -> None: + """Set finetuning task. + + :param task: The type of task to run. Possible values include: "ChatCompletion" + "TextCompletion", "TextClassification", "QuestionAnswering","TextSummarization", + "TokenClassification", "TextTranslation", "ImageClassification", "ImageInstanceSegmentation", + "ImageObjectDetection","VideoMultiObjectTracking",. + :type task: str + + :return: None + """ + self._task = task + + @property + def model(self) -> Optional[Input]: + """The model to be fine-tuned. + :return: Input object representing the mlflow model to be fine-tuned. + :rtype: Input + """ + return self._model + + @model.setter + def model(self, value: Input) -> None: + """Set the model to be fine-tuned. + + :param value: Input object representing the mlflow model to be fine-tuned. + :type value: Input + :raises ValidationException: Expected a mlflow model input. + """ + if isinstance(value, Input) and (cast(Input, value).type in ("mlflow_model", "custom_model")): + self._model = value + else: + msg = "Expected a mlflow model input or custom model input." + raise ValidationException( + message=msg, + no_personal_data_message=msg, + target=ErrorTarget.FINETUNING, + error_category=ErrorCategory.USER_ERROR, + ) + + @property + def model_provider(self) -> Optional[str]: + """The model provider. + :return: The model provider. + :rtype: str + """ + return self._model_provider + + @model_provider.setter + def model_provider(self, value: str) -> None: + """Set the model provider. + + :param value: The model provider. + :type value: str + """ + self._model_provider = RestModelProvider[camel_to_snake(value).upper()] if value else None + + @property + def training_data(self) -> Input: + """Get training data. + + :return: Training data input + :rtype: Input + """ + return self._training_data + + @training_data.setter + def training_data(self, training_data: Input) -> None: + """Set training data. + + :param training_data: Training data input + :type training_data: Input + """ + self._training_data = training_data + + @property + def validation_data(self) -> Optional[Input]: + """Get validation data. + + :return: Validation data input + :rtype: Input + """ + return self._validation_data + + @validation_data.setter + def validation_data(self, validation_data: Input) -> None: + """Set validation data. + + :param validation_data: Validation data input + :type validation_data: Input + """ + self._validation_data = validation_data + + def _resolve_inputs(self, rest_job: RestFineTuningVertical) -> None: + """Resolve JobInputs to UriFileJobInput within data_settings. + + :param rest_job: The rest job object. + :type rest_job: RestFineTuningVertical + """ + if isinstance(rest_job.training_data, Input): + rest_job.training_data = UriFileJobInput(uri=rest_job.training_data.path) + if isinstance(rest_job.validation_data, Input): + rest_job.validation_data = UriFileJobInput(uri=rest_job.validation_data.path) + if isinstance(rest_job.model, Input): + rest_job.model = MLFlowModelJobInput(uri=rest_job.model.path) + + def _restore_inputs(self) -> None: + """Restore UriFileJobInputs to JobInputs within data_settings.""" + if isinstance(self.training_data, UriFileJobInput): + self.training_data = Input(type=AssetTypes.URI_FILE, path=self.training_data.uri) + if isinstance(self.validation_data, UriFileJobInput): + self.validation_data = Input(type=AssetTypes.URI_FILE, path=self.validation_data.uri) + if isinstance(self.model, MLFlowModelJobInput): + self.model = Input(type=AssetTypes.MLFLOW_MODEL, path=self.model.uri) + + def __eq__(self, other: object) -> bool: + """Returns True if both instances have the same values. + + This method check instances equality and returns True if both of + the instances have the same attributes with the same values. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + if not isinstance(other, FineTuningVertical): + return NotImplemented + + return ( + # TODO: Equality from base class does not work, no current precedence for this + super().__eq__(other) + and self.task == other.task + and self.model == other.model + and self.model_provider == other.model_provider + and self.training_data == other.training_data + and self.validation_data == other.validation_data + ) + + def __ne__(self, other: object) -> bool: + """Check inequality between two FineTuningJob objects. + + :param other: Any object + :type other: object + :return: True or False + :rtype: bool + """ + return not self.__eq__(other) |
