diff --git a/aixplain/cli_groups.py b/aixplain/cli_groups.py index c5f05826..ea5e28be 100644 --- a/aixplain/cli_groups.py +++ b/aixplain/cli_groups.py @@ -21,7 +21,7 @@ CLI Runner """ import click -from aixplain.factories.cli.model_factory_cli import list_host_machines, list_functions, create_asset_repo, asset_repo_login, onboard_model, deploy_huggingface_model, get_huggingface_model_status +from aixplain.factories.cli.model_factory_cli import list_host_machines, list_functions, create_asset_repo, asset_repo_login, onboard_model, deploy_huggingface_model, get_huggingface_model_status, list_gpus @click.group('cli') def cli(): @@ -51,6 +51,7 @@ def onboard(): create.add_command(create_asset_repo) list.add_command(list_host_machines) list.add_command(list_functions) +list.add_command(list_gpus) get.add_command(asset_repo_login) get.add_command(get_huggingface_model_status) onboard.add_command(onboard_model) @@ -58,4 +59,4 @@ def onboard(): def run_cli(): - cli() \ No newline at end of file + cli() diff --git a/aixplain/factories/cli/model_factory_cli.py b/aixplain/factories/cli/model_factory_cli.py index 264fadd9..b83d61cc 100644 --- a/aixplain/factories/cli/model_factory_cli.py +++ b/aixplain/factories/cli/model_factory_cli.py @@ -44,7 +44,7 @@ def list_host_machines(api_key: Optional[Text] = None) -> None: click.echo(ret_val_yaml) @click.command("functions") -@click.option("--verbose", default=False, +@click.option("--verbose", is_flag=True, help="List all function details, False by default.") @click.option("--api-key", default=None, help="TEAM_API_KEY if not already set in environment.") @@ -62,21 +62,37 @@ def list_functions(verbose: bool, api_key: Optional[Text] = None) -> None: ret_val_yaml = yaml.dump(ret_val) click.echo(ret_val_yaml) +@click.command("gpus") +@click.option("--api-key", default=None, + help="TEAM_API_KEY if not already set in environment.") +def list_gpus(api_key: Optional[Text] = None) -> None: + """CLI wrapper function for the LIST_GPUS function in ModelFactory. + + Args: + api_key (Text, optional): Team API key. Defaults to None. + Returns: + None + """ + ret_val = ModelFactory.list_gpus(api_key) + ret_val_yaml = yaml.dump(ret_val) + click.echo(ret_val_yaml) + @click.command("image-repo") @click.option("--name", help="Model name.") -@click.option("--hosting-machine", - help="Hosting machine code obtained from LIST_HOSTS.") -@click.option("--version", help="Model version.") @click.option("--description", help="Description of model.") @click.option("--function", help="Function name obtained from LIST_FUNCTIONS.") @click.option("--source-language", default="en", help="Model source language in 2-character 639-1 code or 3-character 639-3 code.") +@click.option("--input-modality", help="Input type (text, video, image, etc.)") +@click.option("--output-modality", help="Output type (text, video, image, etc.)") +@click.option("--documentation-url", default="", help="Link to model documentation.") @click.option("--api-key", default=None, help="TEAM_API_KEY if not already set in environment.") -def create_asset_repo(name: Text, hosting_machine: Text, version: Text, - description: Text, function: Text, - source_language: Text, - api_key: Optional[Text] = None) -> None: +def create_asset_repo(name: Text, description: Text, function: Text, + source_language: Text, input_modality: Text, + output_modality: Text, + documentation_url: Optional[Text] = "", + api_key: Optional[Text] = None) -> None: """CLI wrapper function for the CREATE_ASSET_REPO function in ModelFactory. Args: @@ -93,9 +109,10 @@ def create_asset_repo(name: Text, hosting_machine: Text, version: Text, Returns: None """ - ret_val = ModelFactory.create_asset_repo(name, hosting_machine, version, - description, function, - source_language, api_key) + ret_val = ModelFactory.create_asset_repo(name, description, function, + source_language, input_modality, + output_modality, documentation_url, + api_key) ret_val_yaml = yaml.dump(ret_val) click.echo(ret_val_yaml) @@ -119,8 +136,10 @@ def asset_repo_login(api_key: Optional[Text] = None) -> None: @click.option("--model-id", help="Model ID from CREATE_IMAGE_REPO.") @click.option("--image-tag", help="The tag of the image that you would like hosted.") @click.option("--image-hash", help="The hash of the image you would like onboarded.") +@click.option("--host-machine", default="", help="The machine on which to host the model.") @click.option("--api-key", default=None, help="TEAM_API_KEY if not already set in environment.") def onboard_model(model_id: Text, image_tag: Text, image_hash: Text, + host_machine: Optional[Text] = "", api_key: Optional[Text] = None) -> None: """CLI wrapper function for the ONBOARD_MODEL function in ModelFactory. @@ -132,17 +151,20 @@ def onboard_model(model_id: Text, image_tag: Text, image_hash: Text, Returns: None """ - ret_val = ModelFactory.onboard_model(model_id, image_tag, image_hash, api_key) + ret_val = ModelFactory.onboard_model(model_id, image_tag, image_hash, + host_machine, api_key) ret_val_yaml = yaml.dump(ret_val) click.echo(ret_val_yaml) @click.command("hf-model") @click.option("--name", help="User-defined name for Hugging Face model.") @click.option("--hf-repo-id", help="Repository ID from Hugging Face in {supplier}/{model name} form.") -@click.option("--hf-token", help="Hugging Face token used to authenticate to this model.") +@click.option("--revision", default="", help="Commit hash of repository.") +@click.option("--hf-token", default=None, help="Hugging Face token used to authenticate to this model.") @click.option("--api-key", default=None, help="TEAM_API_KEY if not already set in environment.") def deploy_huggingface_model(name: Text, hf_repo_id: Text, hf_token: Optional[Text] = None, + revision: Optional[Text] = None, api_key: Optional[Text] = None) -> None: """CLI wrapper function for the DEPLOY_HUGGINGFACE_MODEL function in ModelFactory. @@ -153,7 +175,7 @@ def deploy_huggingface_model(name: Text, hf_repo_id: Text, Returns: None """ - ret_val = ModelFactory.deploy_huggingface_model(name, hf_repo_id, hf_token, api_key) + ret_val = ModelFactory.deploy_huggingface_model(name, hf_repo_id, revision, hf_token, api_key) ret_val_yaml = yaml.dump(ret_val) click.echo(ret_val_yaml) @@ -172,4 +194,4 @@ def get_huggingface_model_status(model_id: Text, api_key: Optional[Text] = None) """ ret_val = ModelFactory.get_huggingface_model_status(model_id, api_key) ret_val_yaml = yaml.dump(ret_val) - click.echo(ret_val_yaml) \ No newline at end of file + click.echo(ret_val_yaml) diff --git a/aixplain/factories/model_factory.py b/aixplain/factories/model_factory.py index 221fd94d..0fb845f1 100644 --- a/aixplain/factories/model_factory.py +++ b/aixplain/factories/model_factory.py @@ -270,6 +270,25 @@ def list_host_machines(cls, api_key: Optional[Text] = None) -> List[Dict]: for dictionary in response_dicts: del dictionary["id"] return response_dicts + + @classmethod + def list_gpus(cls, api_key: Optional[Text] = None) -> List[List[Text]]: + """List GPU names on which you can host your language model. + + Args: + api_key (Text, optional): Team API key. Defaults to None. + + Returns: + List[List[Text]]: List of all available GPUs and their prices. + """ + gpu_url = urljoin(config.BACKEND_URL, "sdk/model-onboarding/gpus") + if api_key: + headers = {"Authorization": f"Token {api_key}", "Content-Type": "application/json"} + else: + headers = {"Authorization": f"Token {config.TEAM_API_KEY}", "Content-Type": "application/json"} + response = _request_with_retry("get", gpu_url, headers=headers) + response_list = json.loads(response.text) + return response_list @classmethod def list_functions(cls, verbose: Optional[bool] = False, api_key: Optional[Text] = None) -> List[Dict]: @@ -310,12 +329,13 @@ def list_functions(cls, verbose: Optional[bool] = False, api_key: Optional[Text] def create_asset_repo( cls, name: Text, - hosting_machine: Text, - version: Text, description: Text, function: Text, source_language: Text, - api_key: Optional[Text] = None, + input_modality: Text, + output_modality: Text, + documentation_url: Optional[Text] = "", + api_key: Optional[Text] = None ) -> Dict: """Creates an image repository for this model and registers it in the platform backend. @@ -342,27 +362,36 @@ def create_asset_repo( function_id = function_dict["id"] if function_id is None: raise Exception("Invalid function name") - create_url = urljoin(config.BACKEND_URL, "sdk/models/register") + create_url = urljoin(config.BACKEND_URL, f"sdk/models/onboard") logging.debug(f"URL: {create_url}") if api_key: headers = {"x-api-key": f"{api_key}", "Content-Type": "application/json"} else: headers = {"x-api-key": f"{config.TEAM_API_KEY}", "Content-Type": "application/json"} - always_on = False - is_async = False # Hard-coded to False for first release + payload = { - "name": name, - "hostingMachine": hosting_machine, - "alwaysOn": always_on, - "version": version, - "description": description, - "function": function_id, - "isAsync": is_async, - "sourceLanguage": source_language, + "model": { + "name": name, + "description": description, + "connectionType": [ + "synchronous" + ], + "function": function_id, + "modalities": [ + f"{input_modality}-{output_modality}" + ], + "documentationUrl": documentation_url, + "sourceLanguage": source_language + }, + "source": "aixplain-ecr", + "onboardingParams": { + } } - payload = json.dumps(payload) logging.debug(f"Body: {str(payload)}") - response = _request_with_retry("post", create_url, headers=headers, data=payload) + response = _request_with_retry("post", create_url, headers=headers, json=payload) + + assert response.status_code == 201 + return response.json() @classmethod @@ -379,20 +408,23 @@ def asset_repo_login(cls, api_key: Optional[Text] = None) -> Dict: login_url = urljoin(config.BACKEND_URL, "sdk/ecr/login") logging.debug(f"URL: {login_url}") if api_key: - headers = {"x-api-key": f"{api_key}", "Content-Type": "application/json"} + headers = {"Authorization": f"Token {api_key}", "Content-Type": "application/json"} else: - headers = {"x-api-key": f"{config.TEAM_API_KEY}", "Content-Type": "application/json"} + headers = {"Authorization": f"Token {config.TEAM_API_KEY}", "Content-Type": "application/json"} response = _request_with_retry("post", login_url, headers=headers) + print(f"Response: {response}") response_dict = json.loads(response.text) return response_dict @classmethod - def onboard_model(cls, model_id: Text, image_tag: Text, image_hash: Text, api_key: Optional[Text] = None) -> Dict: + def onboard_model(cls, model_id: Text, image_tag: Text, image_hash: Text, host_machine: Optional[Text] = "", api_key: Optional[Text] = None) -> Dict: """Onboard a model after its image has been pushed to ECR. Args: model_id (Text): Model ID obtained from CREATE_ASSET_REPO. image_tag (Text): Image tag to be onboarded. + image_hash (Text): Image digest. + host_machine (Text, optional): Machine on which to host model. api_key (Text, optional): Team API key. Defaults to None. Returns: Dict: Backend response @@ -403,18 +435,18 @@ def onboard_model(cls, model_id: Text, image_tag: Text, image_hash: Text, api_ke headers = {"x-api-key": f"{api_key}", "Content-Type": "application/json"} else: headers = {"x-api-key": f"{config.TEAM_API_KEY}", "Content-Type": "application/json"} - payload = {"image": image_tag, "sha": image_hash} - payload = json.dumps(payload) + payload = {"image": image_tag, "sha": image_hash, "hostMachine": host_machine} logging.debug(f"Body: {str(payload)}") - response = _request_with_retry("post", onboard_url, headers=headers, data=payload) - message = "Your onboarding request has been submitted to an aiXplain specialist for finalization. We will notify you when the process is completed." - logging.info(message) + response = _request_with_retry("post", onboard_url, headers=headers, json=payload) + if response.status_code == 201: + message = "Your onboarding request has been submitted to an aiXplain specialist for finalization. We will notify you when the process is completed." + logging.info(message) + else: + message = "An error has occurred. Please make sure your model_id is valid and your host_machine, if set, is a valid option from the LIST_GPUS function." return response @classmethod - def deploy_huggingface_model( - cls, name: Text, hf_repo_id: Text, hf_token: Optional[Text] = "", api_key: Optional[Text] = None - ) -> Dict: + def deploy_huggingface_model(cls, name: Text, hf_repo_id: Text, revision: Optional[Text] = "", hf_token: Optional[Text] = "", api_key: Optional[Text] = None) -> Dict: """Onboards and deploys a Hugging Face large language model. Args: @@ -441,7 +473,12 @@ def deploy_huggingface_model( "sourceLanguage": "en", }, "source": "huggingface", - "onboardingParams": {"hf_model_name": model_name, "hf_supplier": supplier, "hf_token": hf_token}, + "onboardingParams": { + "hf_supplier": supplier, + "hf_model_name": model_name, + "hf_token": hf_token, + "revision": revision + } } response = _request_with_retry("post", deploy_url, headers=headers, json=body) logging.debug(response.text) diff --git a/aixplain/modules/metric.py b/aixplain/modules/metric.py index d591772b..16bf4541 100644 --- a/aixplain/modules/metric.py +++ b/aixplain/modules/metric.py @@ -97,7 +97,6 @@ def run( reference (Optional[Union[str, List[str]]], optional): Can give a single reference or a list of references for metric calculation. Defaults to None. """ from aixplain.factories.model_factory import ModelFactory - model = ModelFactory.get(self.id) payload = { "function": self.function, diff --git a/aixplain/modules/model/__init__.py b/aixplain/modules/model/__init__.py index 12c96977..06811332 100644 --- a/aixplain/modules/model/__init__.py +++ b/aixplain/modules/model/__init__.py @@ -267,7 +267,6 @@ def check_finetune_status(self, after_epoch: Optional[int] = None): """ from aixplain.enums.asset_status import AssetStatus from aixplain.modules.finetune.status import FinetuneStatus - headers = {"x-api-key": self.api_key, "Content-Type": "application/json"} resp = None try: @@ -278,7 +277,6 @@ def check_finetune_status(self, after_epoch: Optional[int] = None): finetune_status = AssetStatus(resp["finetuneStatus"]) model_status = AssetStatus(resp["modelStatus"]) logs = sorted(resp["logs"], key=lambda x: float(x["epoch"])) - target_epoch = None if after_epoch is not None: logs = [log for log in logs if float(log["epoch"]) > after_epoch] @@ -286,7 +284,6 @@ def check_finetune_status(self, after_epoch: Optional[int] = None): target_epoch = float(logs[0]["epoch"]) elif len(logs) > 0: target_epoch = float(logs[-1]["epoch"]) - if target_epoch is not None: log = None for log_ in logs: @@ -298,7 +295,6 @@ def check_finetune_status(self, after_epoch: Optional[int] = None): log["trainLoss"] = log_["trainLoss"] if log_["evalLoss"] is not None: log["evalLoss"] = log_["evalLoss"] - status = FinetuneStatus( status=finetune_status, model_status=model_status, diff --git a/docs/user/user_doc.md b/docs/user/user_doc.md index a7fa2178..4466e121 100644 --- a/docs/user/user_doc.md +++ b/docs/user/user_doc.md @@ -76,75 +76,66 @@ response = model.run( ``` ### Deploying Hugging Face Large Language Models + You can deploy your very own Hugging Face large language models on our platform using the aiXplain SDK: ```console -$ aixplain onboard hf-model --name --hf-repo-id --hf-token [--api-key ] +$ aixplain onboard hf-model --name --hf-repo-id --revision --hf-token [--api-key ] ``` This command will return your model's ID. The on-boarding process will take 5 to 15 minutes, during which you can check the on-boarding status by running the following: ```console $ aixplain get hf-model-status --model-id [--api-key ] ``` -Once the on-boarding process has completed, you can use this newly-deployed large language model just like any other model on our platform. Note that our platform currently only supports language models up 7 billion parameters in size (~30 GB), so any attempts to deploy larger models will result in an error message. +Once the on-boarding process has completed, you can use this newly-deployed large language model just like any other private model on our platform. Note that our platform currently only supports language models up 7 billion parameters in size (~30 GB), so any attempts to deploy larger models will result in an error message. ### Uploading Models +## Uploading Models In addition to exploring and running models, the aiXplain SDK allows you to upload your own models to the aiXplain platform. This requires a working model image in line with the template specified [here](https://github.com/aixplain/model-interfaces/blob/main/docs/user/model_setup.md). [These](https://github.com/aixplain/model-interfaces/tree/main) are the interfaces with which you will be working. You will also be required to have an aiXplain account as well as a TEAM_API_KEY which should be set either as an environment variable or passed into each of the following functions. -First, choose a hosting machine appropriate for your model. Note down the host machines "code". You can list the available hosting machines' specifications by running the following: -```console -$ aixplain list hosts [--api-key ] -- code: aix-2c-8g-od - cores: 2 - hourlyCost: 0.12 - memory: 8 - type: on-demand -- code: aix-2c-8g - cores: 2 - hourlyCost: 0.096 - memory: 8 - type: always-on - ... -``` Note: For any of the CLI commands, running `aixplain [verb] [resource] --help` will display a description of each argument that should be passed into that command. The `api-key` parameter is optional and is only used if the environment variable isn't set or you would like to override the existing environment variable. Find a supported function type that best describes your model's purpose. Note down the function's ID. ```console -$ aixplain list functions [--verbose ] [--api-key ] -filteredFrom: 55 +aixplain list functions [--verbose] [--api-key ] +filteredFrom: 63 items: -- name: Language Identification -- name: OCR -- name: Image Label Detection -- name: Video Forced Alignment -- name: Offensive Language Identification -- name: Audio Forced Alignment -- name: Video Generation -- name: Split On Silence -- name: Referenceless Audio Generation Metric -- name: Audio Generation Metric -- name: Speaker Diarization Video -- name: Referenceless Text Generation Metric Default +- modalities: + - text-number + name: Object Detection +- modalities: + - text-label + name: Language Identification +- modalities: + - image-text + - document-text + name: OCR +- modalities: + - image-label + name: Image Label Detection +- modalities: + - image-text + name: Image Captioning ... ``` -`verbose` is optional and is set to False by default, meaning only the function names are listed. Setting this to True will additionally list the function ID, output, and params. Again, `api-key` is optional. +`verbose` is optional and is set to False by default. Again, `api-key` is optional. Once you have chosen a suitable host machine and function, register your model and create an image repository: ```console -$ aixplain create image-repo --name --hosting-machine --version --description --function --source-language [--api-key ] +aixplain create image-repo --name --description --function --source-language --input-modality --output-modality --documentation-url [--api-key ] { "repoName": , "modelId": } ``` -`name` is your model's name. `hosting-machine` should include the code of the hosting machine you would like to use. The `version` field should be set to your model's version number. `description` should hold a short summary of your model's purpose. Specify the function name most closely describe your model's purpose in the `function` field. Finally, `source-language` should contain your model's source language. +`name` is your model's name. `description` should hold a short summary of your model's purpose. Specify the function name most closely describe your model's purpose in the `function` field. Finally, `source-language` should contain your model's source language. This returns a model ID and a repository name. Next, obtain login credentials for the newly created repository: ```console -$ aixplain get image-repo-login [--api-key ] +aixplain get image-repo-login [--api-key ] { "username": , "password": , @@ -152,34 +143,45 @@ $ aixplain get image-repo-login [--api-key ] } ``` -These credentials are valid for 12 hours, after which you much again log in for a fresh set of valid credentials. If you are using Docker, you can use these credentials to log in with the following: +These credentials are valid for 12 hours, after which you must again log in for a fresh set of valid credentials. If you are using Docker, you can use these credentials to log in with the following: ```console docker login --username $USERNAME --password $PASSWORD 535945872701.dkr.ecr.us-east-1.amazonaws.com ``` You must first build your image using the following: ```console -$ docker build . -t 535945872701.dkr.ecr.us-east-1.amazonaws.com/: +docker build . -t $REGISTRY/$REPO_NAME: ``` -where the `` is that returned by `aixplain create image-repo` and `` is some sort of descriptor (usually version number) for your specific model. +where `` is some sort of descriptor (usually a version tag like v0.0.1) for your specific model. -Next, tag your image to match the registry and repository name given in the previous steps. If you are using Docker, this would look like the following: +Push the newly tagged image to the corresponding repository: ```console -$ docker tag {$REGISTRY}/{$REPO_NAME}: +$ docker push $REGISTRY/$REPO_NAME: ``` -Push the newly tagged image to the corresponding repository: + +Once this is done, onboard the model: ```console -$ docker push {$REGISTRY}/{$REPO_NAME}: +$ aixplain onboard model --model-id --image-tag --image-hash --host-machine [--api-key ] ``` +`model-id` should be the model ID returned by the image-create-repo function used earlier. `image-tag` should be set to whatever string you used to tag your model image. The image sha256 hash can be obtained by running `docker images --digests`. Choose the hash corresponding to the image you would like onboarded. `host-machine` should contain the machine code on which to host the model. A list of all the models can be obtained via `aixplain list gpus` as follow: -Once this is done, onboard the model: +Note down the host machines "code": ```console -$ aixplain onboard model --model-id --image-tag --image-hash [--api-key ] +aixplain list gpus [--api-key ] +- - nvidia-t4-1 + - 'Price: 0.752' + - 'Units: $/hr' +- - nvidia-a10g-1 + - 'Price: 1.006' + - 'Units: $/hr' +- - nvidia-a10g-4 + - 'Price: 5.672' + - 'Units: $/hr' + ... ``` -`model-id` should be the model ID returned by the image-create-repo function used earlier. `image-tag` should be set to whatever string you used to tag your model image. The image sha256 hash can be obtained by running `docker images --digests`. Choose the hash corresponding to the image you would like onboarded. -This will send an email to an aiXplain associate to finalize the onboarding process. +This will send an email to an aiXplain associate to finalize the onboarding process. ## Pipelines [Design](https://aixplain.com/platform/studio/) is aiXplain’s no-code AI pipeline builder tool that accelerates AI development by providing a seamless experience to build complex AI systems and deploy them within minutes. You can visit our platform and design your own custom pipeline [here](https://platform.aixplain.com/studio). diff --git a/tests/functional/finetune/data/finetune_test_cost_estimation.json b/tests/functional/finetune/data/finetune_test_cost_estimation.json index 80f4d331..44707255 100644 --- a/tests/functional/finetune/data/finetune_test_cost_estimation.json +++ b/tests/functional/finetune/data/finetune_test_cost_estimation.json @@ -9,4 +9,4 @@ {"model_name": "MPT 7b storywriter", "model_id": "6551a870bf42e6037ab109db", "dataset_name": "Test text generation dataset"}, {"model_name": "BloomZ 7b", "model_id": "6551ab17bf42e6037ab109e0", "dataset_name": "Test text generation dataset"}, {"model_name": "BloomZ 7b MT", "model_id": "656e80147ca71e334752d5a3", "dataset_name": "Test text generation dataset"} -] \ No newline at end of file +] diff --git a/tests/functional/finetune/data/finetune_test_end2end.json b/tests/functional/finetune/data/finetune_test_end2end.json index 80768de9..f744f0e6 100644 --- a/tests/functional/finetune/data/finetune_test_end2end.json +++ b/tests/functional/finetune/data/finetune_test_end2end.json @@ -10,7 +10,7 @@ { "model_name": "aiR", "model_id": "6499cc946eb5633de15d82a1", - "dataset_name": "Test search dataset", + "dataset_name": "Test search dataset metadata", "inference_data": "Hello!", "required_dev": false, "search_metadata": false @@ -23,4 +23,4 @@ "required_dev": false, "search_metadata": false } -] \ No newline at end of file +] diff --git a/tests/functional/finetune/finetune_functional_test.py b/tests/functional/finetune/finetune_functional_test.py index ffa9ad5a..7b45613c 100644 --- a/tests/functional/finetune/finetune_functional_test.py +++ b/tests/functional/finetune/finetune_functional_test.py @@ -130,4 +130,4 @@ def test_prompt_validator(validate_prompt_input_map): finetune = FinetuneFactory.create( str(uuid.uuid4()), dataset_list, model, prompt_template=validate_prompt_input_map["prompt_template"] ) - assert exc_info.type is AssertionError \ No newline at end of file + assert exc_info.type is AssertionError