-
Notifications
You must be signed in to change notification settings - Fork 50
[AQUA] Added support for deploy stack model. #1223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/model_group
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,9 +147,9 @@ def create( | |
freeform_tags: Optional[Dict] = None, | ||
defined_tags: Optional[Dict] = None, | ||
**kwargs, | ||
) -> DataScienceModel: | ||
) -> Union[DataScienceModel, DataScienceModelGroup]: | ||
""" | ||
Creates a custom Aqua model from a service model. | ||
Creates a custom Aqua model or model group from a service model. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -167,28 +167,21 @@ def create( | |
|
||
Returns | ||
------- | ||
DataScienceModel | ||
The instance of DataScienceModel. | ||
Union[DataScienceModel, DataScienceModelGroup] | ||
The instance of DataScienceModel or DataScienceModelGroup. | ||
""" | ||
fine_tune_weights = ( | ||
model_id.fine_tune_weights | ||
if isinstance(model_id, AquaMultiModelRef) | ||
else [] | ||
) | ||
model_id = ( | ||
model_id.model_id if isinstance(model_id, AquaMultiModelRef) else model_id | ||
) | ||
service_model = DataScienceModel.from_id(model_id) | ||
target_project = project_id or PROJECT_OCID | ||
target_compartment = compartment_id or COMPARTMENT_OCID | ||
|
||
# Skip model copying if it is registered model or fine-tuned model | ||
if ( | ||
service_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None) is not None | ||
or service_model.freeform_tags.get(Tags.AQUA_FINE_TUNED_MODEL_TAG) | ||
is not None | ||
): | ||
logger.info( | ||
f"Aqua Model {model_id} already exists in the user's compartment." | ||
"Skipped copying." | ||
) | ||
return service_model | ||
|
||
# combine tags | ||
combined_freeform_tags = { | ||
**(service_model.freeform_tags or {}), | ||
|
@@ -199,23 +192,66 @@ def create( | |
**(defined_tags or {}), | ||
} | ||
|
||
custom_model = ( | ||
DataScienceModel() | ||
.with_compartment_id(target_compartment) | ||
.with_project_id(target_project) | ||
.with_model_file_description(json_dict=service_model.model_file_description) | ||
.with_display_name(service_model.display_name) | ||
.with_description(service_model.description) | ||
.with_freeform_tags(**combined_freeform_tags) | ||
.with_defined_tags(**combined_defined_tags) | ||
.with_custom_metadata_list(service_model.custom_metadata_list) | ||
.with_defined_metadata_list(service_model.defined_metadata_list) | ||
.with_provenance_metadata(service_model.provenance_metadata) | ||
.create(model_by_reference=True, **kwargs) | ||
) | ||
logger.info( | ||
f"Aqua Model {custom_model.id} created with the service model {model_id}." | ||
) | ||
custom_model = None | ||
if fine_tune_weights: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn’t it be better, instead of handling everything in one place, to split the implementation into two separate methods, one for creating the MC record and another for creating the group? |
||
custom_model = ( | ||
DataScienceModelGroup() | ||
.with_compartment_id(target_compartment) | ||
.with_project_id(target_project) | ||
.with_display_name(service_model.display_name) | ||
.with_description(service_model.description) | ||
.with_freeform_tags(**combined_freeform_tags) | ||
.with_defined_tags(**combined_defined_tags) | ||
.with_custom_metadata_list(service_model.custom_metadata_list) | ||
.with_base_model_id(model_id) | ||
.with_member_models( | ||
[ | ||
{ | ||
"inference_key": fine_tune_weight.model_name, | ||
"model_id": fine_tune_weight.model_id, | ||
} | ||
for fine_tune_weight in fine_tune_weights | ||
] | ||
) | ||
.create() | ||
) | ||
|
||
logger.info( | ||
f"Aqua Model Group {custom_model.id} created with the service model {model_id}." | ||
) | ||
else: | ||
# Skip model copying if it is registered model or fine-tuned model | ||
if ( | ||
service_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None) | ||
is not None | ||
or service_model.freeform_tags.get(Tags.AQUA_FINE_TUNED_MODEL_TAG) | ||
is not None | ||
): | ||
logger.info( | ||
f"Aqua Model {model_id} already exists in the user's compartment." | ||
"Skipped copying." | ||
) | ||
return service_model | ||
|
||
custom_model = ( | ||
DataScienceModel() | ||
.with_compartment_id(target_compartment) | ||
.with_project_id(target_project) | ||
.with_model_file_description( | ||
json_dict=service_model.model_file_description | ||
) | ||
.with_display_name(service_model.display_name) | ||
.with_description(service_model.description) | ||
.with_freeform_tags(**combined_freeform_tags) | ||
.with_defined_tags(**combined_defined_tags) | ||
.with_custom_metadata_list(service_model.custom_metadata_list) | ||
.with_defined_metadata_list(service_model.defined_metadata_list) | ||
.with_provenance_metadata(service_model.provenance_metadata) | ||
.create(model_by_reference=True, **kwargs) | ||
) | ||
logger.info( | ||
f"Aqua Model {custom_model.id} created with the service model {model_id}." | ||
) | ||
|
||
# Track unique models that were created in the user's compartment | ||
self.telemetry.record_event_async( | ||
|
@@ -271,6 +307,16 @@ def create_multi( | |
DataScienceModelGroup | ||
Instance of DataScienceModelGroup object. | ||
""" | ||
member_model_ids = [{"model_id": model.model_id} for model in models] | ||
for model in models: | ||
if model.fine_tune_weights: | ||
member_model_ids.extend( | ||
[ | ||
{"model_id": fine_tune_model.model_id} | ||
for fine_tune_model in model.fine_tune_weights | ||
] | ||
) | ||
|
||
custom_model_group = ( | ||
DataScienceModelGroup() | ||
.with_compartment_id(compartment_id) | ||
|
@@ -281,7 +327,7 @@ def create_multi( | |
.with_defined_tags(**(defined_tags or {})) | ||
.with_custom_metadata_list(model_custom_metadata) | ||
# TODO: add member model inference key | ||
.with_member_models([{"model_id": model.model_id for model in models}]) | ||
.with_member_models(member_model_ids) | ||
) | ||
custom_model_group.create() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,4 @@ | |
|
||
DEFAULT_WAIT_TIME = 12000 | ||
DEFAULT_POLL_INTERVAL = 10 | ||
DEFAULT_DEPLOYMENT_TYPE = "MODEL_STACK" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,11 +24,13 @@ | |
from ads.aqua.common.errors import AquaRuntimeError, AquaValueError | ||
from ads.aqua.common.utils import ( | ||
DEFINED_METADATA_TO_FILE_MAP, | ||
build_params_string, | ||
build_pydantic_error_message, | ||
find_restricted_params, | ||
get_combined_params, | ||
get_container_params_type, | ||
get_ocid_substring, | ||
get_params_dict, | ||
get_params_list, | ||
get_preferred_compatible_family, | ||
get_resource_name, | ||
|
@@ -61,7 +63,11 @@ | |
ModelDeploymentConfigSummary, | ||
MultiModelDeploymentConfigLoader, | ||
) | ||
from ads.aqua.modeldeployment.constants import DEFAULT_POLL_INTERVAL, DEFAULT_WAIT_TIME | ||
from ads.aqua.modeldeployment.constants import ( | ||
DEFAULT_DEPLOYMENT_TYPE, | ||
DEFAULT_POLL_INTERVAL, | ||
DEFAULT_WAIT_TIME, | ||
) | ||
from ads.aqua.modeldeployment.entities import ( | ||
AquaDeployment, | ||
AquaDeploymentDetail, | ||
|
@@ -76,6 +82,7 @@ | |
AQUA_DEPLOYMENT_CONTAINER_CMD_VAR_METADATA_NAME, | ||
AQUA_DEPLOYMENT_CONTAINER_METADATA_NAME, | ||
AQUA_DEPLOYMENT_CONTAINER_URI_METADATA_NAME, | ||
AQUA_MODEL_DEPLOYMENT_FOLDER, | ||
AQUA_TELEMETRY_BUCKET, | ||
AQUA_TELEMETRY_BUCKET_NS, | ||
COMPARTMENT_OCID, | ||
|
@@ -162,6 +169,7 @@ def create( | |
cmd_var (Optional[List[str]]): Command variables for the container runtime. | ||
freeform_tags (Optional[Dict]): Freeform tags for model deployment. | ||
defined_tags (Optional[Dict]): Defined tags for model deployment. | ||
deployment_type (Optional[str]): The type of model deployment. | ||
|
||
Returns | ||
------- | ||
|
@@ -206,13 +214,28 @@ def create( | |
|
||
# Create an AquaModelApp instance once to perform the deployment creation. | ||
model_app = AquaModelApp() | ||
if create_deployment_details.model_id: | ||
if ( | ||
create_deployment_details.model_id | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this assumption is a bit wrong, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we talked about this before, that is for single dployment, we adopt |
||
or create_deployment_details.deployment_type == DEFAULT_DEPLOYMENT_TYPE | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a bit dangerous, what if someone change the default deployment type? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add a validation for it and only |
||
): | ||
model_id = create_deployment_details.model_id | ||
if not model_id: | ||
if len(create_deployment_details.models) != 1: | ||
raise AquaValueError( | ||
"Invalid 'models' provided. Only one base model is required for model stack deployment." | ||
) | ||
model_id = create_deployment_details.models[0] | ||
|
||
service_model_id = ( | ||
model_id if isinstance(model_id, str) else model_id.model_id | ||
) | ||
logger.debug( | ||
f"Single model ({create_deployment_details.model_id}) provided. " | ||
f"Single model ({service_model_id}) provided. " | ||
"Delegating to single model creation method." | ||
) | ||
|
||
aqua_model = model_app.create( | ||
model_id=create_deployment_details.model_id, | ||
model_id=model_id, | ||
compartment_id=compartment_id, | ||
project_id=project_id, | ||
freeform_tags=freeform_tags, | ||
|
@@ -677,7 +700,7 @@ def _build_model_group_config( | |
|
||
def _create( | ||
self, | ||
aqua_model: DataScienceModel, | ||
aqua_model: Union[DataScienceModel, DataScienceModelGroup], | ||
create_deployment_details: CreateModelDeploymentDetails, | ||
container_config: Dict, | ||
) -> AquaDeployment: | ||
|
@@ -711,7 +734,10 @@ def _create( | |
tags.update({Tags.TASK: aqua_model.freeform_tags.get(Tags.TASK, UNKNOWN)}) | ||
|
||
# Set up info to get deployment config | ||
config_source_id = create_deployment_details.model_id | ||
config_source_id = ( | ||
create_deployment_details.model_id | ||
or create_deployment_details.models[0].model_id | ||
) | ||
model_name = aqua_model.display_name | ||
|
||
# set up env and cmd var | ||
|
@@ -862,6 +888,20 @@ def _create( | |
deployment_params = get_combined_params(config_params, user_params) | ||
|
||
params = f"{params} {deployment_params}".strip() | ||
|
||
if isinstance(aqua_model, DataScienceModelGroup): | ||
env_var.update({"VLLM_ALLOW_RUNTIME_LORA_UPDATING": "true"}) | ||
env_var.update( | ||
{"MODEL": f"{AQUA_MODEL_DEPLOYMENT_FOLDER}{aqua_model.base_model_id}/"} | ||
) | ||
|
||
params_dict = get_params_dict(params) | ||
# updates `--served-model-name` with service model id | ||
params_dict.update({"--served-model-name": aqua_model.base_model_id}) | ||
# adds `--enable_lora` to parameters | ||
params_dict.update({"--enable_lora": UNKNOWN}) | ||
params = build_params_string(params_dict) | ||
|
||
if params: | ||
env_var.update({"PARAMS": params}) | ||
env_vars = container_spec.env_vars if container_spec else [] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
ModelGroup, | ||
ModelGroupDetails, | ||
ModelGroupSummary, | ||
StackedModelGroupDetails, | ||
UpdateModelGroupDetails, | ||
) | ||
except ModuleNotFoundError as err: | ||
|
@@ -511,28 +512,38 @@ def create( | |
|
||
def _build_model_group_details(self) -> dict: | ||
"""Builds model group details dict for creating or updating oci model group.""" | ||
model_group_details = HomogeneousModelGroupDetails( | ||
custom_metadata_list=[ | ||
CustomMetadata( | ||
key=custom_metadata.key, | ||
value=custom_metadata.value, | ||
description=custom_metadata.description, | ||
category=custom_metadata.category, | ||
) | ||
for custom_metadata in self.custom_metadata_list._to_oci_metadata() | ||
] | ||
) | ||
custom_metadata_list = [ | ||
CustomMetadata( | ||
key=custom_metadata.key, | ||
value=custom_metadata.value, | ||
description=custom_metadata.description, | ||
category=custom_metadata.category, | ||
) | ||
for custom_metadata in self.custom_metadata_list._to_oci_metadata() | ||
] | ||
member_model_details = [ | ||
MemberModelDetails(**member_model) for member_model in self.member_models | ||
] | ||
|
||
if self.base_model_id: | ||
model_group_details = StackedModelGroupDetails( | ||
custom_metadata_list=custom_metadata_list, | ||
base_model_id=self.base_model_id, | ||
) | ||
member_model_details.append(MemberModelDetails(model_id=self.base_model_id)) | ||
else: | ||
model_group_details = HomogeneousModelGroupDetails( | ||
custom_metadata_list=custom_metadata_list | ||
) | ||
|
||
member_model_entries = MemberModelEntries( | ||
member_model_details=[ | ||
MemberModelDetails(**member_model) | ||
for member_model in self.member_models | ||
] | ||
member_model_details=member_model_details | ||
) | ||
|
||
build_model_group_details = copy.deepcopy(self._spec) | ||
build_model_group_details.pop(self.CONST_CUSTOM_METADATA_LIST) | ||
build_model_group_details.pop(self.CONST_MEMBER_MODELS) | ||
build_model_group_details.pop(self.CONST_CUSTOM_METADATA_LIST, None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment here, why we are doing this? |
||
build_model_group_details.pop(self.CONST_MEMBER_MODELS, None) | ||
build_model_group_details.pop(self.CONST_BASE_MODEL_ID, None) | ||
build_model_group_details.update( | ||
{ | ||
self.CONST_COMPARTMENT_ID: self.compartment_id or COMPARTMENT_OCID, | ||
|
@@ -581,6 +592,9 @@ def _update_from_oci_model( | |
) | ||
self.set_spec(self.CONST_CUSTOM_METADATA_LIST, model_custom_metadata) | ||
|
||
if hasattr(model_group_details, "base_model_id"): | ||
self.set_spec(self.CONST_BASE_MODEL_ID, model_group_details.base_model_id) | ||
|
||
# only updates member_models when oci_model_group_instance is an instance of | ||
# oci.data_science.models.ModelGroup as oci.data_science.models.ModelGroupSummary | ||
# doesn't have member_model_entries property. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like there are several place where we do same validation. Wouldn't it be better to have something like: