Skip to content

Commit b92d5fc

Browse files
committed
Merge conflicts
2 parents fc757f6 + ab20473 commit b92d5fc

File tree

16 files changed

+345
-91
lines changed

16 files changed

+345
-91
lines changed

ads/aqua/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def __init__(self) -> None:
6464
set_auth("resource_principal")
6565
self._auth = default_signer({"service_endpoint": OCI_ODSC_SERVICE_ENDPOINT})
6666
self.ds_client = oc.OCIClientFactory(**self._auth).data_science
67+
self.compute_client = oc.OCIClientFactory(**default_signer()).compute
6768
self.logging_client = oc.OCIClientFactory(**default_signer()).logging_management
6869
self.identity_client = oc.OCIClientFactory(**default_signer()).identity
6970
self.region = extract_region(self._auth)

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/common/utils.py

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,9 +1158,11 @@ def validate_cmd_var(cmd_var: List[str], overrides: List[str]) -> List[str]:
11581158

11591159

11601160
def build_pydantic_error_message(ex: ValidationError):
1161-
"""Added to handle error messages from pydantic model validator.
1161+
"""
1162+
Added to handle error messages from pydantic model validator.
11621163
Combine both loc and msg for errors where loc (field) is present in error details, else only build error
1163-
message using msg field."""
1164+
message using msg field.
1165+
"""
11641166

11651167
return {
11661168
".".join(map(str, e["loc"])): e["msg"]
@@ -1185,67 +1187,71 @@ def is_pydantic_model(obj: object) -> bool:
11851187

11861188
@cached(cache=TTLCache(maxsize=1, ttl=timedelta(minutes=5), timer=datetime.now))
11871189
def load_gpu_shapes_index(
1188-
auth: Optional[Dict] = None,
1190+
auth: Optional[Dict[str, Any]] = None,
11891191
) -> GPUShapesIndex:
11901192
"""
1191-
Loads the GPU shapes index from Object Storage or a local resource folder.
1193+
Load the GPU shapes index, preferring the OS bucket copy over the local one.
11921194
1193-
The function first attempts to load the file from an Object Storage bucket using fsspec.
1194-
If the loading fails (due to connection issues, missing file, etc.), it falls back to
1195-
loading the index from a local file.
1195+
Attempts to read `gpu_shapes_index.json` from OCI Object Storage first;
1196+
if that succeeds, those entries will override the local defaults.
11961197
11971198
Parameters
11981199
----------
1199-
auth: (Dict, optional). Defaults to None.
1200-
The default authentication is set using `ads.set_auth` API. If you need to override the
1201-
default, use the `ads.common.auth.api_keys` or `ads.common.auth.resource_principal` to create appropriate
1202-
authentication signer and kwargs required to instantiate IdentityClient object.
1200+
auth
1201+
Optional auth dict (as returned by `ads.common.auth.default_signer()`)
1202+
to pass through to `fsspec.open()`.
12031203
12041204
Returns
12051205
-------
1206-
GPUShapesIndex: The parsed GPU shapes index.
1206+
GPUShapesIndex
1207+
Merged index where any shape present remotely supersedes the local entry.
12071208
12081209
Raises
12091210
------
1210-
FileNotFoundError: If the GPU shapes index cannot be found in either Object Storage or locally.
1211-
json.JSONDecodeError: If the JSON is malformed.
1211+
json.JSONDecodeError
1212+
If any of the JSON is malformed.
12121213
"""
12131214
file_name = "gpu_shapes_index.json"
1214-
data: Dict[str, Any] = {}
12151215

1216-
# Check if the CONDA_BUCKET_NS environment variable is set.
1216+
# Try remote load
1217+
remote_data: Dict[str, Any] = {}
12171218
if CONDA_BUCKET_NS:
12181219
try:
12191220
auth = auth or authutil.default_signer()
1220-
# Construct the object storage path. Adjust bucket name and path as needed.
12211221
storage_path = (
12221222
f"oci://{CONDA_BUCKET_NAME}@{CONDA_BUCKET_NS}/service_pack/{file_name}"
12231223
)
1224-
logger.debug("Loading GPU shapes index from Object Storage")
1225-
with fsspec.open(storage_path, mode="r", **auth) as file_obj:
1226-
data = json.load(file_obj)
1227-
logger.debug("Successfully loaded GPU shapes index.")
1228-
except Exception as ex:
12291224
logger.debug(
1230-
f"Failed to load GPU shapes index from Object Storage. Details: {ex}"
1225+
"Loading GPU shapes index from Object Storage: %s", storage_path
12311226
)
1232-
1233-
# If loading from Object Storage failed, load from the local resource folder.
1234-
if not data:
1235-
try:
1236-
local_path = os.path.join(
1237-
os.path.dirname(__file__), "../resources", file_name
1238-
)
1239-
logger.debug(f"Loading GPU shapes index from {local_path}.")
1240-
with open(local_path) as file_obj:
1241-
data = json.load(file_obj)
1242-
logger.debug("Successfully loaded GPU shapes index.")
1243-
except Exception as e:
1227+
with fsspec.open(storage_path, mode="r", **auth) as f:
1228+
remote_data = json.load(f)
12441229
logger.debug(
1245-
f"Failed to load GPU shapes index from {local_path}. Details: {e}"
1230+
"Loaded %d shapes from Object Storage",
1231+
len(remote_data.get("shapes", {})),
12461232
)
1233+
except Exception as ex:
1234+
logger.debug("Remote load failed (%s); falling back to local", ex)
1235+
1236+
# Load local copy
1237+
local_data: Dict[str, Any] = {}
1238+
local_path = os.path.join(os.path.dirname(__file__), "../resources", file_name)
1239+
try:
1240+
logger.debug("Loading GPU shapes index from local file: %s", local_path)
1241+
with open(local_path) as f:
1242+
local_data = json.load(f)
1243+
logger.debug(
1244+
"Loaded %d shapes from local file", len(local_data.get("shapes", {}))
1245+
)
1246+
except Exception as ex:
1247+
logger.debug("Local load GPU shapes index failed (%s)", ex)
1248+
1249+
# Merge: remote shapes override local
1250+
local_shapes = local_data.get("shapes", {})
1251+
remote_shapes = remote_data.get("shapes", {})
1252+
merged_shapes = {**local_shapes, **remote_shapes}
12471253

1248-
return GPUShapesIndex(**data)
1254+
return GPUShapesIndex(shapes=merged_shapes)
12491255

12501256

12511257
def get_preferred_compatible_family(selected_families: set[str]) -> str:

ads/aqua/extension/ui_handler.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ads.aqua.extension.utils import validate_function_parameters
1616
from ads.aqua.model.entities import ImportModelDetails
1717
from ads.aqua.ui import AquaUIApp
18-
from ads.config import COMPARTMENT_OCID
18+
from ads.config import COMPARTMENT_OCID, IS_BYOR_ENABLED
1919

2020

2121
@dataclass
@@ -82,6 +82,10 @@ def get(self, id=""):
8282
return self.is_bucket_versioned()
8383
elif paths.startswith("aqua/containers"):
8484
return self.list_containers()
85+
elif paths.startswith("aqua/capacityreservations/enabled"):
86+
return self.is_capacity_reservations_enabled()
87+
elif paths.startswith("aqua/capacityreservations"):
88+
return self.list_capacity_reservations()
8589
else:
8690
raise HTTPError(400, f"The request {self.request.path} is invalid.")
8791

@@ -103,6 +107,19 @@ def list_log_groups(self, **kwargs):
103107
AquaUIApp().list_log_groups(compartment_id=compartment_id, **kwargs)
104108
)
105109

110+
def is_capacity_reservations_enabled(self):
111+
"""Checks if the tenant is whitelisted for BYOR (Bring your own reservation) feature."""
112+
return self.finish({"status": str(IS_BYOR_ENABLED).strip().lower() == "true"})
113+
114+
def list_capacity_reservations(self, **kwargs):
115+
"""Lists users compute reservations in a specified compartment."""
116+
compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID)
117+
return self.finish(
118+
AquaUIApp().list_capacity_reservations(
119+
compartment_id=compartment_id, **kwargs
120+
)
121+
)
122+
106123
def list_logs(self, log_group_id: str, **kwargs):
107124
"""Lists the specified log group's log objects."""
108125
return self.finish(AquaUIApp().list_logs(log_group_id=log_group_id, **kwargs))
@@ -279,4 +296,5 @@ def post(self, *args, **kwargs):
279296
("bucket/versioning/?([^/]*)", AquaUIHandler),
280297
("containers/?([^/]*)", AquaUIHandler),
281298
("cli/?([^/]*)", AquaCLIHandler),
299+
("capacityreservations/?([^/]*)", AquaUIHandler),
282300
]

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: 14 additions & 31 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
51-
from ads.aqua.model.constants import AquaModelMetadataKeys, ModelCustomMetadataFields, ModelType
49+
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

ads/aqua/ui.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ def list_logs(self, **kwargs) -> str:
9090
res = self.logging_client.list_logs(log_group_id=log_group_id, **kwargs).data
9191
return sanitize_response(oci_client=self.logging_client, response=res)
9292

93+
@telemetry(entry_point="plugin=ui&action=list_capacity_reservations", name="aqua")
94+
def list_capacity_reservations(self, **kwargs) -> list:
95+
"""
96+
Lists users compute reservations in a specified compartment
97+
98+
Returns
99+
-------
100+
json representation of `oci.core.models.ComputeCapacityReservationSummary`.
101+
102+
"""
103+
compartment_id = kwargs.pop("compartment_id", COMPARTMENT_OCID)
104+
logger.info(f"Loading Capacity reservations from compartment: {compartment_id}")
105+
106+
reservations = self.compute_client.list_compute_capacity_reservations(
107+
compartment_id=compartment_id, **kwargs
108+
)
109+
return sanitize_response(
110+
oci_client=self.compute_client, response=reservations.data
111+
)
112+
93113
@telemetry(entry_point="plugin=ui&action=list_compartments", name="aqua")
94114
def list_compartments(self) -> str:
95115
"""Lists the compartments in a tenancy specified by TENANCY_OCID env variable. This is a pass through the OCI list_compartments

0 commit comments

Comments
 (0)