Skip to content

Commit ab20473

Browse files
elizjomrDzurb
andauthored
Enable AQUA SDK & CLI to Deploy Fine-Tuned LLMs in Multi-Model Deployment (#1175)
Co-authored-by: Dmitrii Cherkasov <[email protected]>
1 parent c57f3fa commit ab20473

File tree

6 files changed

+183
-43
lines changed

6 files changed

+183
-43
lines changed

ads/aqua/common/entities.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ class AquaMultiModelRef(Serializable):
157157
Optional environment variables to override during deployment.
158158
artifact_location : Optional[str]
159159
Artifact path of model in the multimodel group.
160+
fine_tune_weights_location : Optional[str]
161+
For fine tuned models, the artifact path of the modified model weights
160162
"""
161163

162164
model_id: str = Field(..., description="The model OCID to deploy.")
@@ -171,6 +173,9 @@ class AquaMultiModelRef(Serializable):
171173
artifact_location: Optional[str] = Field(
172174
None, description="Artifact path of model in the multimodel group."
173175
)
176+
fine_tune_weights_location: Optional[str] = Field(
177+
None, description="For fine tuned models, the artifact path of the modified model weights"
178+
)
174179

175180
class Config:
176181
extra = "ignore"

ads/aqua/model/model.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
ModelValidationResult,
8484
)
8585
from ads.aqua.model.enums import MultiModelSupportedTaskType
86+
from ads.aqua.model.utils import (
87+
extract_base_model_from_ft,
88+
extract_fine_tune_artifacts_path,
89+
)
8690
from ads.common.auth import default_signer
8791
from ads.common.oci_resource import SEARCH_TYPE, OCIResource
8892
from ads.common.utils import (
@@ -311,12 +315,21 @@ def create_multi(
311315
# "Currently only service models are supported for multi model deployment."
312316
# )
313317

318+
# check if model is a fine-tuned model and if so, add the fine tuned weights path to the fine_tune_weights_location pydantic field
319+
is_fine_tuned_model = Tags.AQUA_FINE_TUNED_MODEL_TAG in source_model.freeform_tags
320+
321+
if is_fine_tuned_model:
322+
model.model_id, model.model_name = extract_base_model_from_ft(source_model)
323+
model_artifact_path, model.fine_tune_weights_location = extract_fine_tune_artifacts_path(source_model)
324+
325+
else:
326+
# Retrieve model artifact for base models
327+
model_artifact_path = source_model.artifact
328+
314329
display_name_list.append(display_name)
315330

316331
self._extract_model_task(model, source_model)
317332

318-
# Retrieve model artifact
319-
model_artifact_path = source_model.artifact
320333
if not model_artifact_path:
321334
raise AquaValueError(
322335
f"Model '{display_name}' (ID: {model.model_id}) has no artifacts. "

ads/aqua/model/utils.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2025 Oracle and/or its affiliates.
3+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4+
"""AQUA model utils"""
5+
6+
from typing import Dict, Optional, Tuple
7+
8+
from ads.aqua.common.entities import AquaMultiModelRef
9+
from ads.aqua.common.errors import AquaValueError
10+
from ads.aqua.common.utils import get_model_by_reference_paths
11+
from ads.aqua.finetuning.constants import FineTuneCustomMetadata
12+
from ads.common.object_storage_details import ObjectStorageDetails
13+
from ads.model.datascience_model import DataScienceModel
14+
15+
16+
def extract_base_model_from_ft(aqua_model: DataScienceModel) -> Tuple[str, str]:
17+
"""Extracts the model_name and base model OCID (config_source_id) OCID for a fine-tuned model"""
18+
19+
config_source_id = aqua_model.custom_metadata_list.get(
20+
FineTuneCustomMetadata.FINE_TUNE_SOURCE
21+
).value
22+
model_name = aqua_model.custom_metadata_list.get(
23+
FineTuneCustomMetadata.FINE_TUNE_SOURCE_NAME
24+
).value
25+
26+
if not config_source_id or not model_name:
27+
raise AquaValueError(
28+
f"Either {FineTuneCustomMetadata.FINE_TUNE_SOURCE} or {FineTuneCustomMetadata.FINE_TUNE_SOURCE_NAME} is missing "
29+
f"from custom metadata for the model {config_source_id}"
30+
)
31+
32+
return config_source_id, model_name
33+
34+
35+
def extract_fine_tune_artifacts_path(aqua_model: DataScienceModel) -> Tuple[str, str]:
36+
"""Extracts the fine tuning source (fine_tune_output_path) and base model path from the DataScienceModel Object"""
37+
38+
base_model_path, fine_tune_output_path = get_model_by_reference_paths(
39+
aqua_model.model_file_description
40+
)
41+
42+
if not fine_tune_output_path or not ObjectStorageDetails.is_oci_path(
43+
fine_tune_output_path
44+
):
45+
raise AquaValueError(
46+
"Fine tuned output path is not available in the model artifact."
47+
)
48+
49+
os_path = ObjectStorageDetails.from_path(fine_tune_output_path)
50+
fine_tune_output_path = os_path.filepath.rstrip("/")
51+
52+
return base_model_path, fine_tune_output_path

ads/aqua/modeldeployment/deployment.py

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
build_pydantic_error_message,
2626
get_combined_params,
2727
get_container_params_type,
28-
get_model_by_reference_paths,
2928
get_ocid_substring,
3029
get_params_dict,
3130
get_params_list,
@@ -46,9 +45,12 @@
4645
UNKNOWN_DICT,
4746
)
4847
from ads.aqua.data import AquaResourceIdentifier
49-
from ads.aqua.finetuning.finetuning import FineTuneCustomMetadata
5048
from ads.aqua.model import AquaModelApp
5149
from ads.aqua.model.constants import AquaModelMetadataKeys, ModelCustomMetadataFields
50+
from ads.aqua.model.utils import (
51+
extract_base_model_from_ft,
52+
extract_fine_tune_artifacts_path,
53+
)
5254
from ads.aqua.modeldeployment.entities import (
5355
AquaDeployment,
5456
AquaDeploymentConfig,
@@ -211,6 +213,7 @@ def create(
211213
)
212214
else:
213215
model_ids = [model.model_id for model in create_deployment_details.models]
216+
214217
try:
215218
model_config_summary = self.get_multimodel_deployment_config(
216219
model_ids=model_ids, compartment_id=compartment_id
@@ -343,22 +346,6 @@ def _create(
343346
config_source_id = create_deployment_details.model_id
344347
model_name = aqua_model.display_name
345348

346-
is_fine_tuned_model = Tags.AQUA_FINE_TUNED_MODEL_TAG in aqua_model.freeform_tags
347-
348-
if is_fine_tuned_model:
349-
try:
350-
config_source_id = aqua_model.custom_metadata_list.get(
351-
FineTuneCustomMetadata.FINE_TUNE_SOURCE
352-
).value
353-
model_name = aqua_model.custom_metadata_list.get(
354-
FineTuneCustomMetadata.FINE_TUNE_SOURCE_NAME
355-
).value
356-
except ValueError as err:
357-
raise AquaValueError(
358-
f"Either {FineTuneCustomMetadata.FINE_TUNE_SOURCE} or {FineTuneCustomMetadata.FINE_TUNE_SOURCE_NAME} is missing "
359-
f"from custom metadata for the model {config_source_id}"
360-
) from err
361-
362349
# set up env and cmd var
363350
env_var = create_deployment_details.env_var or {}
364351
cmd_var = create_deployment_details.cmd_var or []
@@ -378,19 +365,11 @@ def _create(
378365

379366
env_var.update({"BASE_MODEL": f"{model_path_prefix}"})
380367

381-
if is_fine_tuned_model:
382-
_, fine_tune_output_path = get_model_by_reference_paths(
383-
aqua_model.model_file_description
384-
)
385-
386-
if not fine_tune_output_path:
387-
raise AquaValueError(
388-
"Fine tuned output path is not available in the model artifact."
389-
)
390-
391-
os_path = ObjectStorageDetails.from_path(fine_tune_output_path)
392-
fine_tune_output_path = os_path.filepath.rstrip("/")
368+
is_fine_tuned_model = Tags.AQUA_FINE_TUNED_MODEL_TAG in aqua_model.freeform_tags
393369

370+
if is_fine_tuned_model:
371+
config_source_id, model_name = extract_base_model_from_ft(aqua_model)
372+
_, fine_tune_output_path = extract_fine_tune_artifacts_path(aqua_model)
394373
env_var.update({"FT_MODEL": f"{fine_tune_output_path}"})
395374

396375
container_type_key = self._get_container_type_key(
@@ -647,6 +626,10 @@ def _create_multi(
647626
config_data = {"params": params, "model_path": artifact_path_prefix}
648627
if model.model_task:
649628
config_data["model_task"] = model.model_task
629+
630+
if model.fine_tune_weights_location:
631+
config_data["fine_tune_weights_location"] = model.fine_tune_weights_location
632+
650633
model_config.append(config_data)
651634
model_name_list.append(model.model_name)
652635

tests/unitary/with_extras/aqua/test_deployment.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,21 @@
1919
)
2020
from parameterized import parameterized
2121

22+
import ads.aqua.modeldeployment.deployment
23+
import ads.config
24+
from ads.aqua.app import AquaApp
2225
from ads.aqua.common.entities import (
2326
AquaMultiModelRef,
2427
ComputeShapeSummary,
2528
ModelConfigResult,
2629
)
27-
from ads.aqua.app import AquaApp
28-
from ads.aqua.common.entities import ModelConfigResult
29-
import ads.aqua.modeldeployment.deployment
30-
import ads.config
31-
from ads.aqua.common.entities import AquaMultiModelRef
3230
from ads.aqua.common.enums import Tags
3331
from ads.aqua.common.errors import AquaRuntimeError, AquaValueError
3432
from ads.aqua.config.container_config import (
35-
AquaContainerConfigItem,
3633
AquaContainerConfig,
34+
AquaContainerConfigItem,
3735
)
36+
from ads.aqua.model.enums import MultiModelSupportedTaskType
3837
from ads.aqua.modeldeployment import AquaDeploymentApp, MDInferenceResponse
3938
from ads.aqua.modeldeployment.entities import (
4039
AquaDeployment,
@@ -45,7 +44,6 @@
4544
ModelDeploymentConfigSummary,
4645
ModelParams,
4746
)
48-
from ads.aqua.model.enums import MultiModelSupportedTaskType
4947
from ads.aqua.modeldeployment.utils import MultiModelDeploymentConfigLoader
5048
from ads.model.datascience_model import DataScienceModel
5149
from ads.model.deployment.model_deployment import ModelDeployment
@@ -277,7 +275,7 @@ class TestDataset:
277275
"environment_configuration_type": "OCIR_CONTAINER",
278276
"environment_variables": {
279277
"MODEL_DEPLOY_PREDICT_ENDPOINT": "/v1/completions",
280-
"MULTI_MODEL_CONFIG": '{ "models": [{ "params": "--served-model-name model_one --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_one/5be6479/artifact/", "model_task": "text_embedding"}, {"params": "--served-model-name model_two --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_two/83e9aa1/artifact/", "model_task": "image_text_to_text"}, {"params": "--served-model-name model_three --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_three/83e9aa1/artifact/", "model_task": "code_synthesis"}]}',
278+
"MULTI_MODEL_CONFIG": '{ "models": [{ "params": "--served-model-name model_one --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_one/5be6479/artifact/", "model_task": "text_embedding"}, {"params": "--served-model-name model_two --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_two/83e9aa1/artifact/", "model_task": "image_text_to_text"}, {"params": "--served-model-name model_three --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_three/83e9aa1/artifact/", "model_task": "code_synthesis", "fine_tune_weights_location": "oci://test_bucket@test_namespace/models/ft-models/meta-llama-3b/ocid1.datasciencejob.oc1.iad.<ocid>"}]}',
281279
},
282280
"health_check_port": 8080,
283281
"image": "dsmc://image-name:1.0.0.0",
@@ -489,6 +487,7 @@ class TestDataset:
489487
"model_name": "test_model_1",
490488
"model_task": "text_embedding",
491489
"artifact_location": "test_location_1",
490+
"fine_tune_weights_location" : None
492491
},
493492
{
494493
"env_var": {},
@@ -497,6 +496,7 @@ class TestDataset:
497496
"model_name": "test_model_2",
498497
"model_task": "image_text_to_text",
499498
"artifact_location": "test_location_2",
499+
"fine_tune_weights_location" : None
500500
},
501501
{
502502
"env_var": {},
@@ -505,12 +505,13 @@ class TestDataset:
505505
"model_name": "test_model_3",
506506
"model_task": "code_synthesis",
507507
"artifact_location": "test_location_3",
508+
"fine_tune_weights_location" : "oci://test_bucket@test_namespace/models/ft-models/meta-llama-3b/ocid1.datasciencejob.oc1.iad.<ocid>"
508509
},
509510
],
510511
"model_id": "ocid1.datasciencemodel.oc1.<region>.<OCID>",
511512
"environment_variables": {
512513
"MODEL_DEPLOY_PREDICT_ENDPOINT": "/v1/completions",
513-
"MULTI_MODEL_CONFIG": '{ "models": [{ "params": "--served-model-name model_one --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_one/5be6479/artifact/", "model_task": "text_embedding"}, {"params": "--served-model-name model_two --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_two/83e9aa1/artifact/", "model_task": "image_text_to_text"}, {"params": "--served-model-name model_three --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_three/83e9aa1/artifact/", "model_task": "code_synthesis"}]}',
514+
"MULTI_MODEL_CONFIG": '{ "models": [{ "params": "--served-model-name model_one --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_one/5be6479/artifact/", "model_task": "text_embedding"}, {"params": "--served-model-name model_two --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_two/83e9aa1/artifact/", "model_task": "image_text_to_text"}, {"params": "--served-model-name model_three --tensor-parallel-size 1 --max-model-len 2096", "model_path": "models/model_three/83e9aa1/artifact/", "model_task": "code_synthesis", "fine_tune_weights_location": "oci://test_bucket@test_namespace/models/ft-models/meta-llama-3b/ocid1.datasciencejob.oc1.iad.<ocid>"}]}',
514515
},
515516
"cmd": [],
516517
"console_link": "https://cloud.oracle.com/data-science/model-deployments/ocid1.datasciencemodeldeployment.oc1.<region>.<MD_OCID>?region=region-name",
@@ -971,6 +972,7 @@ class TestDataset:
971972
"model_name": "model_one",
972973
"model_task": "text_embedding",
973974
"artifact_location": "artifact_location_one",
975+
"fine_tune_weights_location": None
974976
},
975977
{
976978
"env_var": {"--test_key_two": "test_value_two"},
@@ -979,6 +981,7 @@ class TestDataset:
979981
"model_name": "model_two",
980982
"model_task": "image_text_to_text",
981983
"artifact_location": "artifact_location_two",
984+
"fine_tune_weights_location": None
982985
},
983986
{
984987
"env_var": {"--test_key_three": "test_value_three"},
@@ -987,6 +990,7 @@ class TestDataset:
987990
"model_name": "model_three",
988991
"model_task": "code_synthesis",
989992
"artifact_location": "artifact_location_three",
993+
"fine_tune_weights_location" : "oci://test_bucket@test_namespace/models/ft-models/meta-llama-3b/ocid1.datasciencejob.oc1.iad.<ocid>"
990994
},
991995
]
992996

@@ -1813,6 +1817,7 @@ def test_create_deployment_for_multi_model(
18131817
model_task="code_synthesis",
18141818
gpu_count=2,
18151819
artifact_location="test_location_3",
1820+
fine_tune_weights_location= "oci://test_bucket@test_namespace/models/ft-models/meta-llama-3b/ocid1.datasciencejob.oc1.iad.<ocid>"
18161821
)
18171822

18181823
result = self.app.create(

0 commit comments

Comments
 (0)